From 84a1ad14f44399a65e3fbdec79fbbd9dc3cccf47 Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Wed, 27 Mar 2019 12:39:46 +0000 Subject: [PATCH 01/21] feat: ethercalc integration --- .circleci/config.yml | 7 +- docker-compose.yml | 17 +- etc/migration/migration_runner.js | 10 +- package-lock.json | 1684 ++++++++++++++++- package.json | 10 +- packages/oae-activity/lib/restmodel.js | 18 + packages/oae-content/config/content.js | 47 +- packages/oae-content/lib/api.js | 366 +++- packages/oae-content/lib/constants.js | 13 +- packages/oae-content/lib/init.js | 29 +- .../oae-content/lib/internal/dao.content.js | 97 +- .../oae-content/lib/internal/dao.ethercalc.js | 118 ++ packages/oae-content/lib/internal/dao.js | 1 + .../oae-content/lib/internal/ethercalc.js | 360 ++++ packages/oae-content/lib/migration.js | 18 +- packages/oae-content/lib/model.js | 4 +- packages/oae-content/lib/rest.js | 376 ++-- packages/oae-content/lib/restmodel.js | 58 + packages/oae-content/lib/search.js | 89 +- packages/oae-content/lib/test/util.js | 68 +- packages/oae-content/tests/test-collabdoc.js | 533 ++---- .../oae-content/tests/test-collabsheet.js | 222 +++ packages/oae-content/tests/test-content.js | 361 ++-- packages/oae-lti/lib/api.js | 26 +- packages/oae-lti/tests/test-lti.js | 848 ++++----- .../lib/processors/collabdoc/collabdoc.js | 60 +- packages/oae-preview-processor/lib/rest.js | 26 +- packages/oae-search/lib/restmodel.js | 2 +- packages/oae-tests/lib/util.js | 32 +- packages/oae-util/lib/cassandra.js | 12 + packages/oae-util/lib/redis.js | 9 +- packages/oae-util/lib/server.js | 71 +- packages/oae-version/lib/api.js | 361 ++-- packages/oae-version/tests/test-version.js | 100 +- 34 files changed, 4354 insertions(+), 1699 deletions(-) create mode 100644 packages/oae-content/lib/internal/dao.ethercalc.js create mode 100644 packages/oae-content/lib/internal/ethercalc.js create mode 100644 packages/oae-content/tests/test-collabsheet.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 7826023e49..db19fd2e6b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: test: docker: - - image: 'alpine:3.8' + - image: "alpine:3.8" environment: TMP: /root/tmp working_directory: ~/Hilary @@ -37,6 +37,7 @@ jobs: printf "\nconfig.search.hosts[0].host = 'oae-elasticsearch';" >> config.js printf "\nconfig.mq.connection.host = ['oae-rabbitmq'];" >> config.js printf "\nconfig.etherpad.hosts[0].host = 'oae-etherpad';" >> config.js + printf "\nconfig.ethercalc.host = 'oae-ethercalc';" >> config.js printf "\nconfig.previews.enabled = true;" >> config.js printf "\nconfig.email.debug = false;" >> config.js printf "\nconfig.email.transport = 'sendmail';" >> config.js @@ -49,13 +50,13 @@ jobs: pip install docker-compose - run: name: Create the containers - command: docker-compose up --no-start --build oae-cassandra oae-redis oae-rabbitmq oae-elasticsearch oae-hilary + command: docker-compose up --no-start --build oae-cassandra oae-redis oae-rabbitmq oae-elasticsearch oae-hilary oae-ethercalc - run: name: Start the containers we need command: | docker-compose up -d oae-cassandra oae-redis oae-rabbitmq oae-elasticsearch sleep 25s - docker-compose up -d oae-etherpad + docker-compose up -d oae-etherpad oae-ethercalc - run: name: Install Hilary dependencies command: | diff --git a/docker-compose.yml b/docker-compose.yml index ea4f37b90d..06d2f41974 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ # docker-compose up # -version: '3' +version: "3" networks: my_network: @@ -35,12 +35,12 @@ services: context: . dockerfile: Dockerfile container_name: oae-hilary - # command: nodemon -L app.js | bunyan # default - # command: grunt test-module:oae-principals # for running tests + # command: nodemon -L app.js | npx bunyan # default + # command: npm test # for running tests extra_hosts: - "admin.oae.com:172.20.0.9" # - "tenant1.oae.com:172.20.0.9" - # - "any.other.host.oae.com:172.20.0.9" + # - "any.other.host.oae.com:172.20.0.9" image: hilary:latest restart: always networks: @@ -132,3 +132,12 @@ services: ports: - 9001:9001 tty: false + oae-ethercalc: + container_name: oae-ethercalc + image: "oaeproject/oae-ethercalc-docker" + restart: always + networks: + - my_network + ports: + - 8000:8000 + tty: false diff --git a/etc/migration/migration_runner.js b/etc/migration/migration_runner.js index 5439205931..9d10a07135 100644 --- a/etc/migration/migration_runner.js +++ b/etc/migration/migration_runner.js @@ -40,11 +40,12 @@ const lookForMigrations = async function(allModules) { if (migrateFileExists.isFile()) { migrationsToRun.push({ name: eachModule, file: migrationFilePath }); } - } catch (e) { + } catch (error) { log().warn('Skipping ' + eachModule); } } } + return migrationsToRun; }; @@ -73,15 +74,16 @@ const runMigrations = async function(dbConfig, callback) { log().error({ err }, 'Error running migration.'); callback(err); } + log().info('Migrations complete'); callback(); } ); }); }); - } catch (e) { - log().error({ err: e }, 'Error running migration.'); - callback(e); + } catch (error) { + log().error({ err: error }, 'Error running migration.'); + callback(error); } }; diff --git a/package-lock.json b/package-lock.json index da2d89791b..bede20c22c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -358,6 +358,52 @@ "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", "integrity": "sha1-R6++GiqSYhkdtoOOT9HTm0CCF0Y=" }, + "adler-32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.0.0.tgz", + "integrity": "sha1-KHKKcXVvYpZm3RZTzYB5Op3xhlE=", + "requires": { + "concat-stream": "^2.0.0", + "exit-on-epipe": "^1.0.1", + "printj": "^1.2.1" + }, + "dependencies": { + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "readable-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.2.0.tgz", + "integrity": "sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "after": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.1.tgz", + "integrity": "sha1-q11PuIP1loFtNRX495HAr0ht1ic=" + }, "agent-base": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", @@ -385,8 +431,7 @@ "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "optional": true + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, "amqp-connection-manager": { "version": "2.3.0", @@ -443,6 +488,11 @@ "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=" }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -539,6 +589,11 @@ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, + "arraybuffer.slice": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", + "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=" + }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -678,6 +733,20 @@ } } }, + "axios": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", + "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", + "requires": { + "follow-redirects": "^1.3.0", + "is-buffer": "^1.1.5" + } + }, + "babyparse": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/babyparse/-/babyparse-0.2.1.tgz", + "integrity": "sha1-Bp8DXfP9zm86RV3V2vx1F43PN2A=" + }, "backoff": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", @@ -741,6 +810,21 @@ } } }, + "base64-arraybuffer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.2.tgz", + "integrity": "sha1-R030qfLaJOBd8xWMOx2zw81GoVQ=" + }, + "base64-url": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-1.2.1.tgz", + "integrity": "sha1-GZ/WYXAqDnt9yubgaYuwicUvbXg=" + }, + "base64id": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-0.1.0.tgz", + "integrity": "sha1-As4P3u4M709ACA4ec+g08LG/zj8=" + }, "base64url": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/base64url/-/base64url-1.0.6.tgz", @@ -750,6 +834,21 @@ "meow": "~2.0.0" } }, + "basic-auth": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.0.tgz", + "integrity": "sha1-ERstn/jk5tE2uMhOpeCWy4c1Fjc=" + }, + "basic-auth-connect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/basic-auth-connect/-/basic-auth-connect-1.0.0.tgz", + "integrity": "sha1-/bC0OWLKe0BFanwrtI/hc9otISI=" + }, + "batch": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.5.1.tgz", + "integrity": "sha1-NqS6tZTAUP17UHvKDbMMLZKvT/I=" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -763,6 +862,14 @@ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "requires": { + "callsite": "1.0.0" + } + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -774,6 +881,15 @@ "integrity": "sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw==", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "bitsyntax": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.1.0.tgz", @@ -805,6 +921,11 @@ } } }, + "blob": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.2.tgz", + "integrity": "sha1-uJVivWmUr5W6HoEhVVNjM6ojzyQ=" + }, "bluebird": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", @@ -939,6 +1060,11 @@ "resolved": "https://registry.npmjs.org/buf-compare/-/buf-compare-1.0.1.tgz", "integrity": "sha1-/vKNqLgROgoNtEMLC2Rntpcws0o=" }, + "buffer-crc32": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.3.tgz", + "integrity": "sha1-u1RRnpXRB8vSQA520MqxRnM22SE=" + }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -997,6 +1123,11 @@ "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", "dev": true }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, "callsites": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", @@ -1042,6 +1173,21 @@ "long": "^2.2.0" } }, + "cfb": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-0.11.1.tgz", + "integrity": "sha1-qW248nKmw/uZ27sj70EiP0i+Hqc=", + "requires": { + "commander": "^2.19.0" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + } + } + }, "chai": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", @@ -1400,6 +1546,54 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, + "codepage": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.14.0.tgz", + "integrity": "sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k=", + "requires": { + "commander": "~2.14.1", + "exit-on-epipe": "~1.0.1" + }, + "dependencies": { + "commander": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", + "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==" + } + } + }, + "coffee-css": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/coffee-css/-/coffee-css-0.0.5.tgz", + "integrity": "sha1-WJqCL7pa4NTxoEJLIrp1AWgydaQ=", + "requires": { + "coffee-script": ">=1.0.0", + "underscore": ">=1.1.6" + } + }, + "coffee-script": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", + "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==" + }, + "coffeecup": { + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/coffeecup/-/coffeecup-0.3.21.tgz", + "integrity": "sha1-VOcUF1yyI93RBWhRcZbgzVZtGYo=", + "requires": { + "coffee-script": ">=1.3 <2", + "optparse": "1.0.3", + "stylus": "0.27.2", + "uglify-js": "1.2.6" + }, + "dependencies": { + "uglify-js": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.2.6.tgz", + "integrity": "sha1-01Sy08HPEOvBj6eMEaKL3ZzhWA0=" + } + } + }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -1449,11 +1643,83 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + }, + "compressible": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-1.1.1.tgz", + "integrity": "sha1-I7ceqQ6mxqZiiXAakYGCwk0HKe8=" + }, + "compression": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.0.11.tgz", + "integrity": "sha1-aXAM8e6JY0VDVqwZKm5ekeIyv/s=", + "requires": { + "accepts": "~1.0.7", + "bytes": "1.0.0", + "compressible": "~1.1.1", + "debug": "1.0.4", + "on-headers": "~1.0.0", + "vary": "~1.0.0" + }, + "dependencies": { + "accepts": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.0.7.tgz", + "integrity": "sha1-W1AftPBwQwmWTM2wSBclQSCNqxo=", + "requires": { + "mime-types": "~1.0.0", + "negotiator": "0.4.7" + } + }, + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=" + }, + "debug": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", + "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=", + "requires": { + "ms": "0.6.2" + } + }, + "mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=" + }, + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=" + }, + "negotiator": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.7.tgz", + "integrity": "sha1-pBYPcXfsgGc4Yx0NMFIyXaQqvcg=" + }, + "vary": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.1.tgz", + "integrity": "sha1-meSYFWaihhGN+yuBc1ffeZM3bRA=" + } + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1532,6 +1798,31 @@ } } }, + "connect-timeout": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/connect-timeout/-/connect-timeout-1.2.2.tgz", + "integrity": "sha1-WVNgK7Zqv9X6Ia6RGnIhxeglocA=", + "requires": { + "debug": "1.0.4", + "ms": "0.6.2", + "on-headers": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", + "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=", + "requires": { + "ms": "0.6.2" + } + }, + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=" + } + } + }, "contains-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", @@ -1610,6 +1901,31 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + } + } + }, + "crc-32": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.0.2.tgz", + "integrity": "sha1-CVB5hO6bzOO9G4hh8N6KsQroGH0=", + "requires": { + "exit-on-epipe": "^1.0.1", + "printj": "^1.2.1" + } + }, "create-error-class": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", @@ -1640,6 +1956,28 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" }, + "csrf": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-2.0.7.tgz", + "integrity": "sha1-0E9S4Paiin4s/h4B3V68JRs9QgE=", + "requires": { + "base64-url": "1.2.1", + "rndm": "~1.1.0", + "scmp": "1.0.0", + "uid-safe": "~1.1.0" + }, + "dependencies": { + "uid-safe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-1.1.0.tgz", + "integrity": "sha1-WNbF2r+N+9jVKDSDmAbAP9YUMjI=", + "requires": { + "base64-url": "1.2.1", + "native-or-bluebird": "~1.1.2" + } + } + } + }, "css-select": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", @@ -1656,6 +1994,33 @@ "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" }, + "cssom": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.2.5.tgz", + "integrity": "sha1-JoJwm1kC5yEt9SkRb/eIzVslSJQ=" + }, + "csurf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.4.1.tgz", + "integrity": "sha1-DMrwJpkrLSGHcdYXT1xsQCpiif0=", + "requires": { + "cookie": "0.1.2", + "cookie-signature": "1.0.4", + "csrf": "~2.0.1" + }, + "dependencies": { + "cookie": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.2.tgz", + "integrity": "sha1-cv7D0k5Io0Mgc9kMEmQgBQYQBLE=" + }, + "cookie-signature": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.4.tgz", + "integrity": "sha1-Dt0iKG46ERuaKnDbNj6SXoZ/aso=" + } + } + }, "csv": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/csv/-/csv-5.1.1.tgz", @@ -1803,8 +2168,7 @@ "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, "deep-strict-equal": { "version": "0.2.0", @@ -2064,6 +2428,13 @@ "resolved": "https://registry.npmjs.org/email-validator/-/email-validator-2.0.4.tgz", "integrity": "sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==" }, + "emitter": { + "version": "http://github.com/component/emitter/archive/1.0.1.tar.gz", + "integrity": "sha512-k3Da+QreMb9waaGCHNAHox5QqxnZEYlQmvIVYwQibrI6OpIRyIIyFGgDV5dXRLr1AJ32JLqEh0VxQEq20dFskw==", + "requires": { + "indexof": "0.0.1" + } + }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -2097,6 +2468,108 @@ "once": "^1.4.0" } }, + "engine.io": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-1.3.1.tgz", + "integrity": "sha1-LZaDCP/65dF/Ugm2d1JG6Q2KcF4=", + "requires": { + "base64id": "0.1.0", + "debug": "0.6.0", + "engine.io-parser": "1.0.6", + "ws": "0.4.31" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" + }, + "debug": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.6.0.tgz", + "integrity": "sha1-zp1dAl1SlLPwdIpJS+uvPJ/Yc08=" + }, + "nan": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-0.3.2.tgz", + "integrity": "sha1-DfGTXKsVNpB17xYK0olBB6oU3C0=" + }, + "ws": { + "version": "0.4.31", + "resolved": "https://registry.npmjs.org/ws/-/ws-0.4.31.tgz", + "integrity": "sha1-WkhJ56nM0e1aga60hHyf7fMSKSc=", + "requires": { + "commander": "~0.6.1", + "nan": "~0.3.0", + "options": ">=0.0.5", + "tinycolor": "0.x" + } + } + } + }, + "engine.io-client": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.3.1.tgz", + "integrity": "sha1-HFpl1cWvbQS0TCLD282Vw57RyYk=", + "requires": { + "component-emitter": "1.1.2", + "component-inherit": "0.0.3", + "debug": "0.7.4", + "engine.io-parser": "1.0.6", + "has-cors": "1.0.3", + "indexof": "0.0.1", + "parsejson": "0.0.1", + "parseqs": "0.0.2", + "parseuri": "0.0.2", + "ws": "0.4.31", + "xmlhttprequest": "https://github.com/LearnBoost/node-XMLHttpRequest/archive/0f36d0b5ebc03d85f860d42a64ae9791e1daa433.tar.gz" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" + }, + "component-emitter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz", + "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=" + }, + "debug": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=" + }, + "nan": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-0.3.2.tgz", + "integrity": "sha1-DfGTXKsVNpB17xYK0olBB6oU3C0=" + }, + "ws": { + "version": "0.4.31", + "resolved": "https://registry.npmjs.org/ws/-/ws-0.4.31.tgz", + "integrity": "sha1-WkhJ56nM0e1aga60hHyf7fMSKSc=", + "requires": { + "commander": "~0.6.1", + "nan": "~0.3.0", + "options": ">=0.0.5", + "tinycolor": "0.x" + } + } + } + }, + "engine.io-parser": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.0.6.tgz", + "integrity": "sha1-04gTFDpBHLO5FBMqsFv5nm96JI4=", + "requires": { + "after": "0.8.1", + "arraybuffer.slice": "0.0.6", + "base64-arraybuffer": "0.1.2", + "blob": "0.0.2", + "utf8": "2.0.0" + } + }, "enhance-visitors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/enhance-visitors/-/enhance-visitors-1.0.0.tgz", @@ -2130,6 +2603,41 @@ "is-arrayish": "^0.2.1" } }, + "errorhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.1.1.tgz", + "integrity": "sha1-GN79Q22Mou/gotiGxcTW7m121pE=", + "requires": { + "accepts": "~1.0.4", + "escape-html": "1.0.1" + }, + "dependencies": { + "accepts": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.0.7.tgz", + "integrity": "sha1-W1AftPBwQwmWTM2wSBclQSCNqxo=", + "requires": { + "mime-types": "~1.0.0", + "negotiator": "0.4.7" + } + }, + "escape-html": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz", + "integrity": "sha1-GBoobq05ejmpKFfPsdQwUuNWv/A=" + }, + "mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=" + }, + "negotiator": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.7.tgz", + "integrity": "sha1-pBYPcXfsgGc4Yx0NMFIyXaQqvcg=" + } + } + }, "es-abstract": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", @@ -2796,6 +3304,48 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "ethercalc": { + "version": "0.20170704.0", + "resolved": "https://registry.npmjs.org/ethercalc/-/ethercalc-0.20170704.0.tgz", + "integrity": "sha1-ROKZPNrE3qIMjp75ykD0or6+78E=", + "requires": { + "cors": "*", + "csv-parse": "^0.0.6", + "iconv-lite": "^0.4.13", + "j": "0.4.x", + "livescript": "1.5.x", + "minimatch": "*", + "nodemailer": "*", + "optimist": "*", + "redis": "0.12.x", + "socialcalc": "2.x", + "uuid-pure": "*", + "webworker-threads": "^0.7.1", + "xoauth2": "*", + "zappajs": "0.5.x" + }, + "dependencies": { + "csv-parse": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-0.0.6.tgz", + "integrity": "sha1-lGEHImUP6sgc9UnCySmGMtK2A3w=" + }, + "redis": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz", + "integrity": "sha1-ZN92rQ/IrOuuvSoGReikj6xJGF4=" + } + } + }, + "ethercalc-client": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/ethercalc-client/-/ethercalc-client-0.0.4.tgz", + "integrity": "sha1-iMVe7qrAqjZuWGflcsSlZXaftM8=", + "requires": { + "axios": "^0.18.0", + "ethercalc": "^0.20170704.0" + } + }, "etherpad-lite-client": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/etherpad-lite-client/-/etherpad-lite-client-0.8.0.tgz", @@ -2845,6 +3395,11 @@ } } }, + "exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" + }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -2952,23 +3507,83 @@ } } }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "express-session": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.7.6.tgz", + "integrity": "sha1-4cNpuiF296/beed9ZdzYx8RuSKU=", "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "buffer-crc32": "0.2.3", + "cookie": "0.1.2", + "cookie-signature": "1.0.4", + "debug": "1.0.4", + "depd": "0.4.4", + "on-headers": "~1.0.0", + "parseurl": "~1.3.0", + "uid-safe": "1.0.1", + "utils-merge": "1.0.0" }, "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "cookie": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.2.tgz", + "integrity": "sha1-cv7D0k5Io0Mgc9kMEmQgBQYQBLE=" + }, + "cookie-signature": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.4.tgz", + "integrity": "sha1-Dt0iKG46ERuaKnDbNj6SXoZ/aso=" + }, + "debug": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", + "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=", + "requires": { + "ms": "0.6.2" + } + }, + "depd": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/depd/-/depd-0.4.4.tgz", + "integrity": "sha1-BwkfrnX5eCjYm0oCotR3jw58BmI=" + }, + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=" + }, + "uid-safe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-1.0.1.tgz", + "integrity": "sha1-W9FIRgouhPVPGT/SA1LIw9feasg=", + "requires": { + "base64-url": "1", + "mz": "1" + } + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "requires": { "is-plain-object": "^2.0.4" @@ -3150,8 +3765,7 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "faye-websocket": { "version": "0.10.0", @@ -3377,6 +3991,29 @@ "resolved": "https://registry.npmjs.org/flex-exec/-/flex-exec-1.0.0.tgz", "integrity": "sha1-BpdLaFMoOdKhLDLevNsSN4IA/fA=" }, + "follow-redirects": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", + "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", + "requires": { + "debug": "^3.2.6" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -3422,6 +4059,11 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" }, + "frac": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/frac/-/frac-0.3.1.tgz", + "integrity": "sha1-V3Z3t/3L5vr3xGHxgB00E3zaQ1Q=" + }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -4287,6 +4929,10 @@ "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", "dev": true }, + "global": { + "version": "https://github.com/component/global/archive/v2.0.1.tar.gz", + "integrity": "sha512-O91OcV/NbdmQJPHaRu2ekSP7bqFRLWgqSwaJvqHPZHUwmHBagQYTOra29+LnzzG3lZkXH1ANzHzfCxtAPM9HMA==" + }, "global-dirs": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", @@ -5005,6 +5651,25 @@ "har-schema": "^2.0.0" } }, + "harb": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/harb/-/harb-0.1.1.tgz", + "integrity": "sha1-uCObri8HJLZaqvLnTp6xWgTbUOs=", + "requires": { + "babyparse": "0.2.1", + "codepage": "^1.14.0", + "commander": "^2.19.0", + "exit-on-epipe": "^1.0.1", + "ssf": "0.8.2" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + } + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -5022,6 +5687,22 @@ "ansi-regex": "^2.0.0" } }, + "has-binary-data": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/has-binary-data/-/has-binary-data-0.1.1.tgz", + "integrity": "sha1-4QdJ+4eCilLflvQIZYfrSgOWZDk=", + "requires": { + "isarray": "0.0.1" + } + }, + "has-cors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.0.3.tgz", + "integrity": "sha1-UCrLmzEE2sM90mMOry+IiwuvTLM=", + "requires": { + "global": "https://github.com/component/global/archive/v2.0.1.tar.gz" + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -5288,6 +5969,11 @@ } } }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -5768,6 +6454,55 @@ "semver": "^5.5.0" } }, + "j": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/j/-/j-0.4.5.tgz", + "integrity": "sha1-As8p8d2+VOUnJj0HVNbo0hemBk4=", + "requires": { + "commander": "^2.19.0", + "concat-stream": "^2.0.0", + "exit-on-epipe": "^1.0.1", + "harb": "~0.1.1", + "xlsjs": "~0.7.6", + "xlsx": "~0.9.1" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "readable-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.2.0.tgz", + "integrity": "sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "jacoco-parse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/jacoco-parse/-/jacoco-parse-2.0.1.tgz", @@ -5973,6 +6708,11 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "json3": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.2.6.tgz", + "integrity": "sha1-9u/JPAagTemuxTBT3yVZuxniA4s=" + }, "json5": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", @@ -6152,6 +6892,11 @@ "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz", "integrity": "sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==" }, + "keypress": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz", + "integrity": "sha1-SjGI1CkbZrT2XtuZ+AaqmuKTWSo=" + }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -6317,7 +7062,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, "requires": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -6350,6 +7094,23 @@ "uc.micro": "^1.0.1" } }, + "livescript": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/livescript/-/livescript-1.5.0.tgz", + "integrity": "sha1-T+cSHEEhfkYI4zTrnL4XYuY+VWY=", + "requires": { + "optionator": "~0.8.1", + "prelude-ls": "~1.1.2", + "source-map": "^0.5.6" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, "load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", @@ -6765,6 +7526,42 @@ "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==", "dev": true }, + "method-override": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/method-override/-/method-override-2.1.3.tgz", + "integrity": "sha1-UR9BxPsdzNtqsYRNpdxuqBt8ETU=", + "requires": { + "debug": "1.0.4", + "methods": "1.1.0", + "parseurl": "~1.3.0", + "vary": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", + "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=", + "requires": { + "ms": "0.6.2" + } + }, + "methods": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.0.tgz", + "integrity": "sha1-XcpO4S31L/OwVhRZhqjwHLyGQ28=" + }, + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=" + }, + "vary": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.1.tgz", + "integrity": "sha1-meSYFWaihhGN+yuBc1ffeZM3bRA=" + } + } + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -7155,6 +7952,42 @@ "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", "optional": true }, + "morgan": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.2.3.tgz", + "integrity": "sha1-Ow8XBN+QJVpUJZGrrNeXiRqMQKE=", + "requires": { + "basic-auth": "1.0.0", + "bytes": "1.0.0", + "depd": "0.4.4", + "on-finished": "2.1.0" + }, + "dependencies": { + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=" + }, + "depd": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/depd/-/depd-0.4.4.tgz", + "integrity": "sha1-BwkfrnX5eCjYm0oCotR3jw58BmI=" + }, + "ee-first": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.0.5.tgz", + "integrity": "sha1-jJshKJjYzZ8alDZlDOe+ICyen/A=" + }, + "on-finished": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.1.0.tgz", + "integrity": "sha1-DFOfCSkej/rd4MiiWFD7LO3HAi0=", + "requires": { + "ee-first": "1.0.5" + } + } + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -7232,6 +8065,16 @@ } } }, + "mz": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-1.3.0.tgz", + "integrity": "sha1-BvCT/dmVagbTfhsegTROJ0eMQvA=", + "requires": { + "native-or-bluebird": "1", + "thenify": "3", + "thenify-all": "1" + } + }, "nan": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", @@ -7261,6 +8104,11 @@ "to-regex": "^3.0.1" } }, + "native-or-bluebird": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.1.2.tgz", + "integrity": "sha1-OSHhECMtHreQ89rGG7NwUxx9NW4=" + }, "natives": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", @@ -8868,6 +9716,11 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-1.0.0.tgz", "integrity": "sha1-5l3Idm07R7S4MHRlyDEdoDCwcKY=" }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" + }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -9118,7 +9971,6 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, "requires": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.4", @@ -9131,11 +9983,20 @@ "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" } } }, + "options": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" + }, + "optparse": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/optparse/-/optparse-1.0.3.tgz", + "integrity": "sha1-L/SaPWkbkLC5ob6RF/KSNz6xvWY=" + }, "os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -9286,6 +10147,30 @@ "@types/node": "*" } }, + "parsejson": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.1.tgz", + "integrity": "sha1-mxDGwNglq1ieaFFTgm3go7oni8w=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseqs": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.2.tgz", + "integrity": "sha1-nf5wss3aw4i95PNbHyQPpYrb5sc=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.2.tgz", + "integrity": "sha1-20GHjy1pZHGL6HCzFAlz2Ak74VY=", + "requires": { + "better-assert": "~1.0.0" + } + }, "parseurl": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", @@ -9662,8 +10547,7 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, "prepend-http": { "version": "1.0.4", @@ -9685,6 +10569,11 @@ "fast-diff": "^1.1.2" } }, + "printj": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.2.1.tgz", + "integrity": "sha512-VR1iadSfok+3MweY9HS2V5ExzwtS3TVvJyJUtdPc8K8dblg2m6H3YOKnlMdZlzp31AaGnM95GKhIFITo6j02OQ==" + }, "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", @@ -10303,6 +11192,14 @@ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, + "response-time": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.0.1.tgz", + "integrity": "sha1-xtLLrerEyyUbIQFv4YJkDAKv80M=", + "requires": { + "on-headers": "~1.0.0" + } + }, "restjsdoc": { "version": "file:packages/restjsdoc", "requires": { @@ -10392,6 +11289,11 @@ } } }, + "rndm": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.1.1.tgz", + "integrity": "sha1-7870N0Ah94tj3mImtZhRICadZPE=" + }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", @@ -10460,6 +11362,11 @@ "ajv-keywords": "^3.1.0" } }, + "scmp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/scmp/-/scmp-1.0.0.tgz", + "integrity": "sha1-oLJyw/xykvdxFWRvAGGLAmJRTgQ=" + }, "secure-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/secure-keys/-/secure-keys-1.0.0.tgz", @@ -10521,6 +11428,52 @@ } } }, + "serve-favicon": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.0.1.tgz", + "integrity": "sha1-SCaXXZ8XPKOkFY6WmBYfdd7Hr+w=", + "requires": { + "fresh": "0.2.2" + }, + "dependencies": { + "fresh": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.2.2.tgz", + "integrity": "sha1-lzHc9WeMf660T7kDxPct9VGH+nc=" + } + } + }, + "serve-index": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.1.6.tgz", + "integrity": "sha1-t1gxj+eBYoOD9mrIDdRHcS6neB8=", + "requires": { + "accepts": "~1.0.7", + "batch": "0.5.1", + "parseurl": "~1.3.0" + }, + "dependencies": { + "accepts": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.0.7.tgz", + "integrity": "sha1-W1AftPBwQwmWTM2wSBclQSCNqxo=", + "requires": { + "mime-types": "~1.0.0", + "negotiator": "0.4.7" + } + }, + "mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=" + }, + "negotiator": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.7.tgz", + "integrity": "sha1-pBYPcXfsgGc4Yx0NMFIyXaQqvcg=" + } + } + }, "serve-static": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", @@ -11194,6 +12147,105 @@ } } }, + "socialcalc": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socialcalc/-/socialcalc-2.3.0.tgz", + "integrity": "sha512-hkK5c7eRRGzOiRNPFCQ/kzv2wqOSFaD4AtYag962r0jV0mlyhTQ7xvs3r14MB3Xbo2hAWq5AKYddrtqvuWvDAA==" + }, + "socket.io": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-1.0.6.tgz", + "integrity": "sha1-tWZTKIja46yQWKEvKUAV69+oCEo=", + "requires": { + "debug": "0.7.4", + "engine.io": "1.3.1", + "has-binary-data": "0.1.1", + "socket.io-adapter": "0.2.0", + "socket.io-client": "1.0.6", + "socket.io-parser": "2.2.0" + }, + "dependencies": { + "debug": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=" + } + } + }, + "socket.io-adapter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.2.0.tgz", + "integrity": "sha1-vTkym4lhNxeH4k80WwdOyc8ADjM=", + "requires": { + "debug": "0.7.4", + "socket.io-parser": "2.1.2" + }, + "dependencies": { + "debug": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=" + }, + "socket.io-parser": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.1.2.tgz", + "integrity": "sha1-h2ZVue3VVcW99zAc7fMKQ2xnuLA=", + "requires": { + "debug": "0.7.4", + "emitter": "http://github.com/component/emitter/archive/1.0.1.tar.gz", + "isarray": "0.0.1", + "json3": "3.2.6" + } + } + } + }, + "socket.io-client": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.0.6.tgz", + "integrity": "sha1-yGyz5QerL5baRQC9NPz0ah6d/l4=", + "requires": { + "component-bind": "1.0.0", + "component-emitter": "1.1.2", + "debug": "0.7.4", + "engine.io-client": "1.3.1", + "has-binary-data": "0.1.1", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseuri": "0.0.2", + "socket.io-parser": "2.2.0", + "to-array": "0.1.3" + }, + "dependencies": { + "component-emitter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz", + "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=" + }, + "debug": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=" + } + } + }, + "socket.io-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.2.0.tgz", + "integrity": "sha1-JglgH1nmp/q0NqU749Mz+7/L0wo=", + "requires": { + "debug": "0.7.4", + "emitter": "http://github.com/component/emitter/archive/1.0.1.tar.gz", + "isarray": "0.0.1", + "json3": "3.2.6" + }, + "dependencies": { + "debug": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=" + } + } + }, "sockjs": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", @@ -11342,6 +12394,23 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, + "ssf": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.8.2.tgz", + "integrity": "sha1-udTcahwbz3b4q/qW19dlb7Kr7NY=", + "requires": { + "colors": "0.6.2", + "frac": "0.3.1", + "voc": "^1.1.0" + }, + "dependencies": { + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" + } + } + }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -11403,6 +12472,14 @@ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", "dev": true }, + "stream-counter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stream-counter/-/stream-counter-0.2.0.tgz", + "integrity": "sha1-3tJmVWMZyLDiIoErnPOyb6fZR94=", + "requires": { + "readable-stream": "~1.1.8" + } + }, "stream-transform": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-1.0.8.tgz", @@ -11487,6 +12564,23 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, + "stylus": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.27.2.tgz", + "integrity": "sha1-ESH3+M0VKw+KSqaiSpreoQyCURc=", + "requires": { + "cssom": "0.2.x", + "debug": "*", + "mkdirp": "0.3.x" + }, + "dependencies": { + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" + } + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -11617,6 +12711,22 @@ "promise": ">=3.2 <8" } }, + "thenify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -11637,6 +12747,11 @@ "resolved": "https://registry.npmjs.org/timezone-js/-/timezone-js-0.4.13.tgz", "integrity": "sha1-T9CdlRAH1STbANrbdfpbWfnSy6w=" }, + "tinycolor": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tinycolor/-/tinycolor-0.0.1.tgz", + "integrity": "sha1-MgtaUtg6u1l42Bo+iH1K77FaYWQ=" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -11645,6 +12760,11 @@ "os-tmpdir": "~1.0.2" } }, + "to-array": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.3.tgz", + "integrity": "sha1-1F2txjY0F/YPKEdP6lDs3btPSZE=" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -11773,7 +12893,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, "requires": { "prelude-ls": "~1.1.2" } @@ -11803,6 +12922,45 @@ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, + "uglify-js": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.4.15.tgz", + "integrity": "sha1-ErxthDRfvDBuE/cHXWQ3qL9k1+M=", + "requires": { + "async": "~0.2.6", + "optimist": "~0.3.5", + "source-map": "0.1.34", + "uglify-to-browserify": "~1.0.0" + }, + "dependencies": { + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=" + }, + "optimist": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", + "requires": { + "wordwrap": "~0.0.2" + } + }, + "source-map": { + "version": "0.1.34", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.34.tgz", + "integrity": "sha1-p8/omux7FoLDsZjQrPtH19CQVms=", + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=" + }, "uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -12051,6 +13209,11 @@ } } }, + "utf8": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.0.0.tgz", + "integrity": "sha1-ec5Z7O2HSAnKuacfxxAsfUXUEY0=" + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -12075,6 +13238,11 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, + "uuid-pure": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/uuid-pure/-/uuid-pure-1.0.10.tgz", + "integrity": "sha1-cvIxtZz2w69en2unuWOpGG0Qm10=" + }, "valid-data-url": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-0.1.6.tgz", @@ -12140,6 +13308,16 @@ } } }, + "vhost": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vhost/-/vhost-2.0.0.tgz", + "integrity": "sha1-HiZ3C9D86GxAlFWR5vKExokXkeI=" + }, + "voc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/voc/-/voc-1.1.0.tgz", + "integrity": "sha512-fthgd8OJLqq8vPcLjElTk6Rcl2e3v5ekcXauImaqEnQqd5yUWKg1+ZOBgS2KTWuVKcuvZMQq4TDptiT1uYddUA==" + }, "vscode-languageserver-types": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz", @@ -12290,6 +13468,16 @@ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" }, + "webworker-threads": { + "version": "0.7.17", + "resolved": "https://registry.npmjs.org/webworker-threads/-/webworker-threads-0.7.17.tgz", + "integrity": "sha512-Y2w2aXBbDLk9IzTEb9u+MsODC3s4YlGI7g4h0t+1OAwIO8yBI9rQL35ZYlyayiCuWu1dZMH/P7kGU8OwW7YsyA==", + "optional": true, + "requires": { + "bindings": "^1.3.0", + "nan": "^2.11.0" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -12491,6 +13679,111 @@ "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" }, + "xlsjs": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/xlsjs/-/xlsjs-0.7.6.tgz", + "integrity": "sha1-2IdUVpqrz47qcMwjlhtGJjSklWU=", + "requires": { + "cfb": "~0.11.0", + "codepage": "^1.14.0", + "commander": "^2.19.0", + "exit-on-epipe": "^1.0.1", + "ssf": "~0.8.1" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + } + } + }, + "xlsx": { + "version": "0.9.13", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.9.13.tgz", + "integrity": "sha1-WGHRHhCh+ZtvK0keLRGad3fQZuc=", + "requires": { + "adler-32": "~1.0.0", + "cfb": "~0.11.1", + "codepage": "~1.8.0", + "commander": "~2.9.0", + "crc-32": "~1.0.2", + "exit-on-epipe": "~1.0.0", + "ssf": "~0.9.1" + }, + "dependencies": { + "codepage": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.8.1.tgz", + "integrity": "sha1-8aAJ1SYdwnVGKLrLb7vw5uKr/6o=", + "requires": { + "commander": "^2.19.0", + "concat-stream": "^2.0.0", + "exit-on-epipe": "^1.0.1", + "voc": "^1.1.0" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + } + } + }, + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" + }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "frac": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.0.6.tgz", + "integrity": "sha1-mg38I5VoUqizIGI7688b6eoEgik=", + "requires": { + "voc": "^1.1.0" + } + }, + "readable-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.2.0.tgz", + "integrity": "sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "ssf": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.9.4.tgz", + "integrity": "sha1-jlepjBnbvx7dU/D4yef9UksPbJw=", + "requires": { + "colors": "0.6.2", + "frac": "~1.0.6", + "voc": "^1.1.0" + } + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "xml2js": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", @@ -12522,6 +13815,10 @@ "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=" }, + "xmlhttprequest": { + "version": "https://github.com/LearnBoost/node-XMLHttpRequest/archive/0f36d0b5ebc03d85f860d42a64ae9791e1daa433.tar.gz", + "integrity": "sha512-TVSZwoeUQ7OKhb8jnQdSxGFz+lm4MGWmhG0deeYg85VQT74x5LcSrKeXHE0ZIzEycgqQ5mF8r8e1AykA7TpNAQ==" + }, "xo": { "version": "0.24.0", "resolved": "https://registry.npmjs.org/xo/-/xo-0.24.0.tgz", @@ -12809,6 +14106,11 @@ } } }, + "xoauth2": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/xoauth2/-/xoauth2-1.2.0.tgz", + "integrity": "sha1-8u76wRRyyXHqO8RuVU60sSMhRuU=" + }, "xregexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", @@ -13086,6 +14388,334 @@ "requires": { "googleapis": "^5.2.1" } + }, + "zappajs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/zappajs/-/zappajs-0.5.0.tgz", + "integrity": "sha1-HMeG0xwVWboYDwHKOt8eE8oCylw=", + "requires": { + "coffee-css": "0.0.5", + "coffeecup": "0.3.21", + "express": "3.16.9", + "methods": "1.1.0", + "node-uuid": "1.4.1", + "socket.io": "1.0.6", + "uglify-js": "2.4.15" + }, + "dependencies": { + "body-parser": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.6.7.tgz", + "integrity": "sha1-gjBr7K30RUPoJrOQfq6T8CN8Tlw=", + "requires": { + "bytes": "1.0.0", + "depd": "0.4.4", + "iconv-lite": "0.4.4", + "media-typer": "0.2.0", + "on-finished": "2.1.0", + "qs": "2.2.2", + "raw-body": "1.3.0", + "type-is": "~1.3.2" + } + }, + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=" + }, + "commander": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-1.3.2.tgz", + "integrity": "sha1-io8w7GcKb91kr1LxkUuQfXnq1bU=", + "requires": { + "keypress": "0.1.x" + } + }, + "connect": { + "version": "2.25.9", + "resolved": "https://registry.npmjs.org/connect/-/connect-2.25.9.tgz", + "integrity": "sha1-loDW8vsgDq2rPScuQZ545dh88x8=", + "requires": { + "basic-auth-connect": "1.0.0", + "body-parser": "~1.6.7", + "bytes": "1.0.0", + "compression": "~1.0.11", + "connect-timeout": "~1.2.2", + "cookie": "0.1.2", + "cookie-parser": "1.3.2", + "cookie-signature": "1.0.4", + "csurf": "~1.4.1", + "debug": "1.0.4", + "depd": "0.4.4", + "errorhandler": "1.1.1", + "express-session": "~1.7.6", + "finalhandler": "0.1.0", + "fresh": "0.2.2", + "media-typer": "0.2.0", + "method-override": "~2.1.3", + "morgan": "~1.2.3", + "multiparty": "3.3.2", + "on-headers": "~1.0.0", + "parseurl": "~1.3.0", + "pause": "0.0.1", + "qs": "2.2.2", + "response-time": "~2.0.1", + "serve-favicon": "2.0.1", + "serve-index": "~1.1.6", + "serve-static": "~1.5.3", + "type-is": "~1.3.2", + "vhost": "2.0.0" + } + }, + "cookie": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.2.tgz", + "integrity": "sha1-cv7D0k5Io0Mgc9kMEmQgBQYQBLE=" + }, + "cookie-parser": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.3.2.tgz", + "integrity": "sha1-UiEcyCyVXXn/DAiJVEB3JOGc9WI=", + "requires": { + "cookie": "0.1.2", + "cookie-signature": "1.0.4" + } + }, + "cookie-signature": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.4.tgz", + "integrity": "sha1-Dt0iKG46ERuaKnDbNj6SXoZ/aso=" + }, + "debug": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", + "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=", + "requires": { + "ms": "0.6.2" + } + }, + "depd": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/depd/-/depd-0.4.4.tgz", + "integrity": "sha1-BwkfrnX5eCjYm0oCotR3jw58BmI=" + }, + "destroy": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.3.tgz", + "integrity": "sha1-tDO0ck5x/YVR2YhRdIUcX8N34sk=" + }, + "ee-first": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.0.5.tgz", + "integrity": "sha1-jJshKJjYzZ8alDZlDOe+ICyen/A=" + }, + "escape-html": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz", + "integrity": "sha1-GBoobq05ejmpKFfPsdQwUuNWv/A=" + }, + "express": { + "version": "3.16.9", + "resolved": "https://registry.npmjs.org/express/-/express-3.16.9.tgz", + "integrity": "sha1-mTdHvlZpcAKA2WgsthrROJOYR/w=", + "requires": { + "basic-auth": "1.0.0", + "buffer-crc32": "0.2.3", + "commander": "1.3.2", + "connect": "2.25.9", + "cookie": "0.1.2", + "cookie-signature": "1.0.4", + "debug": "1.0.4", + "depd": "0.4.4", + "escape-html": "1.0.1", + "fresh": "0.2.2", + "media-typer": "0.2.0", + "merge-descriptors": "0.0.2", + "methods": "1.1.0", + "mkdirp": "0.5.0", + "parseurl": "~1.3.0", + "proxy-addr": "1.0.1", + "range-parser": "1.0.0", + "send": "0.8.3", + "vary": "0.1.0" + } + }, + "finalhandler": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.1.0.tgz", + "integrity": "sha1-2gW7xPX0owyEzh2R88FUAHxOnao=", + "requires": { + "debug": "1.0.4", + "escape-html": "1.0.1" + } + }, + "fresh": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.2.2.tgz", + "integrity": "sha1-lzHc9WeMf660T7kDxPct9VGH+nc=" + }, + "iconv-lite": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.4.tgz", + "integrity": "sha1-6V8uQdsHNfwhZS94J6XuMuY8g6g=" + }, + "ipaddr.js": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-0.1.2.tgz", + "integrity": "sha1-ah/T2FT1ACllw017vNm0qNSwRn4=" + }, + "media-typer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.2.0.tgz", + "integrity": "sha1-2KBlITrf6qLnYyGitt2jb/YzWYQ=" + }, + "merge-descriptors": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-0.0.2.tgz", + "integrity": "sha1-w2pSp4FDdRPFcnXzndnTF1FKyMc=" + }, + "methods": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.0.tgz", + "integrity": "sha1-XcpO4S31L/OwVhRZhqjwHLyGQ28=" + }, + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=" + }, + "mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=" + }, + "mkdirp": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=" + }, + "multiparty": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-3.3.2.tgz", + "integrity": "sha1-Nd5oBNwZZD5SSfPT473GyM4wHT8=", + "requires": { + "readable-stream": "~1.1.9", + "stream-counter": "~0.2.0" + } + }, + "node-uuid": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.1.tgz", + "integrity": "sha1-Oa71EOWImj3KnIlbUGxzquG6wEg=" + }, + "on-finished": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.1.0.tgz", + "integrity": "sha1-DFOfCSkej/rd4MiiWFD7LO3HAi0=", + "requires": { + "ee-first": "1.0.5" + } + }, + "proxy-addr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.1.tgz", + "integrity": "sha1-x8Vm1etOP61n7rnHfFVYzMObiKg=", + "requires": { + "ipaddr.js": "0.1.2" + } + }, + "qs": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.2.2.tgz", + "integrity": "sha1-3+eD8YVLGsKzreknda0D4n4DIYw=" + }, + "range-parser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.0.tgz", + "integrity": "sha1-pLJkz+C+XONqvjdlrJwqJIdG28A=" + }, + "raw-body": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.3.0.tgz", + "integrity": "sha1-l4IwoValVI9C7vFN4i0PT2EAg9E=", + "requires": { + "bytes": "1", + "iconv-lite": "0.4.4" + } + }, + "send": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/send/-/send-0.8.3.tgz", + "integrity": "sha1-WTiGAE/LloobVyeBSjKziLO5kIM=", + "requires": { + "debug": "1.0.4", + "depd": "0.4.4", + "destroy": "1.0.3", + "escape-html": "1.0.1", + "fresh": "0.2.2", + "mime": "1.2.11", + "ms": "0.6.2", + "on-finished": "2.1.0", + "range-parser": "~1.0.0" + } + }, + "serve-static": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.5.4.tgz", + "integrity": "sha1-gZ+zeuRr0C3VILd/z3/Y9REvl4I=", + "requires": { + "escape-html": "1.0.1", + "parseurl": "~1.3.0", + "send": "0.8.5", + "utils-merge": "1.0.0" + }, + "dependencies": { + "send": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/send/-/send-0.8.5.tgz", + "integrity": "sha1-N/cIIW5vUMF150xp/sU0hOL9gsc=", + "requires": { + "debug": "1.0.4", + "depd": "0.4.4", + "destroy": "1.0.3", + "escape-html": "1.0.1", + "fresh": "0.2.2", + "mime": "1.2.11", + "ms": "0.6.2", + "on-finished": "2.1.0", + "range-parser": "~1.0.0" + } + } + } + }, + "type-is": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.3.2.tgz", + "integrity": "sha1-TypdxYd1yhYwJQr8cYb4s2MJ0bs=", + "requires": { + "media-typer": "0.2.0", + "mime-types": "~1.0.1" + } + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" + }, + "vary": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/vary/-/vary-0.1.0.tgz", + "integrity": "sha1-3wlFiZ6TwMxb0YzIMh2dIedPYXY=" + } + } } } } diff --git a/package.json b/package.json index d64bd89527..cf70413bd3 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "dox": "^0.9.0", "elasticsearchclient": "^0.5.3", "ent": "^2.2.0", + "ethercalc-client": "^0.0.4", "etherpad-lite-client": "^0.8.0", "expand-url": "^0.1.3", "express": "4.16.4", @@ -177,7 +178,14 @@ "max-params": "off", "max-nested-callbacks": "off", "prettier/prettier": "error", - "no-use-before-define": "off" + "no-use-before-define": "off", + "import/no-unresolved": [ + 2, + { + "commonjs": true, + "amd": true + } + ] }, "env": [ "mocha" diff --git a/packages/oae-activity/lib/restmodel.js b/packages/oae-activity/lib/restmodel.js index 67ab4230ab..e03d361b73 100644 --- a/packages/oae-activity/lib/restmodel.js +++ b/packages/oae-activity/lib/restmodel.js @@ -53,6 +53,24 @@ * @Property {string} url The URL to the collaborative docuemnt profile */ +/** + * @RESTModel ActivityContentCollabsheet + * + * @Required [displayName,id,oae:id,oae:profilePath,oae:resourceSubType,oae:revisionId,oae:tenant,oae:visibility,objectType,url] + * @Property {string} displayName The display name for the collaborative spreadsheet + * @Property {string} id The API URL for the collaborative spreadsheet + * @Property {ActivityImage} image The thumbnail for the collaborative spreadsheet + * @Property {string} oae:id The id of the collaborative spreadsheet + * @Property {string} oae:profilePath The relative path to the collaborative spreadsheet + * @Property {string} oae:resourceSubType The content item type [collabsheet] + * @Property {string} oae:revisionId The id of the current collaborative spreadsheet revision + * @Property {BasicTenant} oae:tenant The tenant to which this collaborative spreadsheet is associated + * @Property {string} oae:visibility The visibility of the collaborative spreadsheet [loggedin,private,public] + * @Property {ActivityImage} oae:wideImage The wide thumbnail for the file + * @Property {string} objectType The type of activity entity [content] + * @Property {string} url The URL to the collaborative spreadsheet profile + */ + /** * @RESTModel ActivityContentFile * diff --git a/packages/oae-content/config/content.js b/packages/oae-content/config/content.js index 35b502d300..ded102e460 100644 --- a/packages/oae-content/config/content.js +++ b/packages/oae-content/config/content.js @@ -15,57 +15,80 @@ const Fields = require('oae-config/lib/fields'); +const PUBLIC = 'public'; +const PRIVATE = 'private'; +const LOGGEDIN = 'loggedin'; + module.exports = { title: 'OAE Content Module', visibility: { name: 'Default Visibility Values', description: 'Default visibility settings for new content', elements: { - files: new Fields.List('Files Visibility', 'Default visibility for new files', 'public', [ + files: new Fields.List('Files Visibility', 'Default visibility for new files', PUBLIC, [ { name: 'Public', - value: 'public' + value: PUBLIC }, { name: 'Logged in users', - value: 'loggedin' + value: LOGGEDIN }, { name: 'Private', - value: 'private' + value: PRIVATE } ]), collabdocs: new Fields.List( 'Collaborative Document Visibility', 'Default visibility for new Collaborative Documents', - 'private', + PRIVATE, + [ + { + name: 'Public', + value: PUBLIC + }, + { + name: 'Logged in users', + value: LOGGEDIN + }, + { + name: 'Private', + value: PRIVATE + } + ] + ), + collabsheets: new Fields.List( + 'Collaborative Spreadsheet Visibility', + 'Default visibility for new Collaborative Spreadsheets', + PRIVATE, [ { name: 'Public', - value: 'public' + value: PUBLIC }, { name: 'Logged in users', - value: 'loggedin' + value: LOGGEDIN }, { name: 'Private', - value: 'private' + value: PRIVATE } ] ), - links: new Fields.List('Links Visibility', 'Default visibility for new links', 'public', [ + links: new Fields.List('Links Visibility', 'Default visibility for new links', PUBLIC, [ { name: 'Public', - value: 'public' + value: PUBLIC }, { name: 'Logged in users', - value: 'loggedin' + value: LOGGEDIN }, { name: 'Private', - value: 'private' + value: PRIVATE } ]) } diff --git a/packages/oae-content/lib/api.js b/packages/oae-content/lib/api.js index 08d1886bf8..035b8d9e53 100644 --- a/packages/oae-content/lib/api.js +++ b/packages/oae-content/lib/api.js @@ -43,8 +43,12 @@ const { ContentConstants } = require('./constants'); const ContentDAO = require('./internal/dao'); const ContentMembersLibrary = require('./internal/membersLibrary'); const ContentUtil = require('./internal/util'); +const Ethercalc = require('./internal/ethercalc'); const Etherpad = require('./internal/etherpad'); +const COLLABDOC = 'collabdoc'; +const COLLABSHEET = 'collabsheet'; + /** * ### Events * @@ -56,6 +60,7 @@ const Etherpad = require('./internal/etherpad'); * * `deletedContent(ctx, contentObj, members)`: A content item was deleted. The 'ctx', the deleted 'contentObj' and the list of authz principals that had this content item in their library * * `downloadedContent(ctx, content, revision)`: A content item was downloaded. The `ctx`, `content` and the `revision` are all provided. * * `editedCollabdoc(ctx, contentObj)`: A collaborative document was edited by a user without resulting in a new revision. This happens if the revision-creation was already triggered by another user leaving the document + * * `editedCollabsheet(ctx, contentObj)`: A collaborative spreadsheet was edited by a user without resulting in a new revision. This happens if the revision-creation was already triggered by another user leaving the spreadsheet * * `getContentLibrary(ctx, principalId, visibility, start, limit, contentObjects)`: A content library was retrieved. * * `getContentProfile(ctx, content)`: A content profile was retrieved. The `ctx` and the `content` are both provided. * * `restoredContent(ctx, newContentObj, oldContentObj, restoredRevision)`: An older revision for a content item has been restored. @@ -134,6 +139,7 @@ const getFullContentProfile = function(ctx, contentId, callback) { if (err && err.code !== 401) { return callback(err); } + if (err) { isManager = false; } @@ -162,6 +168,7 @@ const _getFullContentProfile = function(ctx, contentObj, isManager, callback) { if (err) { return callback(err); } + contentObj.createdBy = createdBy; // Check if the user can share this content item @@ -170,6 +177,7 @@ const _getFullContentProfile = function(ctx, contentObj, isManager, callback) { if (err && err.code !== 401) { return callback(err); } + if (err) { canShare = false; } @@ -177,8 +185,8 @@ const _getFullContentProfile = function(ctx, contentObj, isManager, callback) { // Specify on the return value if the current user can share the content item contentObj.canShare = canShare; - // For any other than collabdoc, we simply return with the share information - if (contentObj.resourceSubType !== 'collabdoc') { + // For any other than collabdoc or collabsheet, we simply return with the share information + if ((contentObj.resourceSubType !== COLLABDOC) & (contentObj.resourceSubType !== COLLABSHEET)) { emitter.emit(ContentConstants.events.GET_CONTENT_PROFILE, ctx, contentObj); return callback(null, contentObj); } @@ -194,6 +202,7 @@ const _getFullContentProfile = function(ctx, contentObj, isManager, callback) { if (err && err.code !== 401) { return callback(err); } + if (err) { contentObj.isEditor = false; } else { @@ -402,12 +411,14 @@ const _createFile = function(ctx, displayName, description, visibility, file, ad }) .isMediumString(); } + if (file) { validator.check(file.size, { code: 400, msg: 'Missing size on the file object' }).notEmpty(); validator.check(file.size, { code: 400, msg: 'Invalid size on the file object' }).isInt(); validator.check(file.size, { code: 400, msg: 'Invalid size on the file object' }).min(0); validator.check(file.name, { code: 400, msg: 'Missing name on the file object' }).notEmpty(); } + if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -507,7 +518,7 @@ const createCollabDoc = function(ctx, displayName, description, visibility, addi ctx, contentId, revisionId, - 'collabdoc', + COLLABDOC, displayName, description, visibility, @@ -543,17 +554,82 @@ const createCollabDoc = function(ctx, displayName, description, visibility, addi }); }; +/** + * Create a collaborative sheet as a pooled content item + * @param { Context } ctx Standard context object containing the current user and the current tenant + * + * @param { String } displayName The display name of the collaborative spreadsheet + * @param { String } [description] A longer description for the collaborative spreadsheet + * @param { String } [visibility] The visibility of the collaborative spreadsheet.One of`public`, `loggedin`, `private` + * @param { Object } [additionalMembers] Object where the keys represent principal ids that need to be added to the content upon creation and the values represent the role that principal will have.Possible values are "viewer", "editor" and "manager" + * @param { String[] } [folders] The ids of the folders to which this collaborative spreadsheet should be added + * @param { Function } callback Standard callback function* @param { Object } callback.err An error that occurred, if any + * @param { Content } callback.content The created collaborative spreadsheet + */ +const createCollabSheet = function(ctx, displayName, description, visibility, additionalMembers, folders, callback) { + callback = callback || function() {}; + + // Setting content to default if no visibility setting is provided + visibility = visibility || Config.getValue(ctx.tenant().alias, 'visibility', 'collabsheets'); + + const contentId = _generateContentId(ctx.tenant().alias); + const revisionId = _generateRevisionId(contentId); + + Ethercalc.createRoom(contentId, function(err, roomId) { + if (err) { + return callback(err); + } + + _createContent( + ctx, + contentId, + revisionId, + COLLABSHEET, + displayName, + description, + visibility, + additionalMembers, + folders, + { ethercalcRoomId: roomId }, + {}, + function(err, content, revision, memberChangeInfo) { + if (err) { + return callback(err); + } + + content.ethercalcRoomId = roomId; + + emitter.emit( + ContentConstants.events.CREATED_CONTENT, + ctx, + content, + revision, + memberChangeInfo, + folders, + function(err) { + if (err) { + return callback(_.first(err)); + } + + return callback(null, content); + } + ); + } + ); + }); +}; + /** * Create a new piece of pooled content * * @param {Context} ctx Standard context object containing the current user and the current tenant * @param {String} contentId The id of the content item * @param {Strign} revisionId The id of the revision for the content item - * @param {String} resourceSubType The content item type. One of `file`, `collabdoc`, `link` + * @param {String} resourceSubType The content item type. One of `file`, `collabdoc`, `collabsheet`, `link` * @param {String} displayName The display name of the content item * @param {String} [description] A longer description for the content item * @param {String} visibility The visibility of the collaborative document. One of `public`, `loggedin`, `private` - * @param {Object} roles Object where the keys represent principal ids that need to be added to the content upon creation and the values represent the role that principal will have. Possible values are "viewer" and "manager", as well as "editor" for collabdocs + * @param {Object} roles Object where the keys represent principal ids that need to be added to the content upon creation and the values represent the role that principal will have. Possible values are "viewer" and "manager", as well as "editor" for collabdocs and collabsheets * @param {String} folders The ids of the folders to which this content item should be added * @param {Object} otherValues JSON object where the keys represent other metadata values that need to be stored, and the values represent the metadata values * @param {Object} revisionData JSON object where the keys represent revision columns that need to be stored, and the values represent the revision values @@ -597,6 +673,7 @@ const _createContent = function( .check(description, { code: 400, msg: 'A description can only be 10000 characters long' }) .isMediumString(); } + validator .check(visibility, { code: 400, @@ -615,9 +692,10 @@ const _createContent = function( // Ensure all roles applied are valid. Editor is only valid for collabdocs const validRoles = [AuthzConstants.role.VIEWER, AuthzConstants.role.MANAGER]; - if (resourceSubType === 'collabdoc') { + if (resourceSubType === COLLABDOC || resourceSubType === COLLABSHEET) { validRoles.push(AuthzConstants.role.EDITOR); } + _.each(roles, role => { validator .check(role, { @@ -702,6 +780,7 @@ const canManageFolders = function(ctx, folderIds, callback) { if (err) { return callback(err); } + if (folders.length !== folderIds.length) { return callback({ code: 400, msg: 'One or more folders do not exist' }); } @@ -832,6 +911,7 @@ const handlePublish = function(data, callback) { if (err) { return callback(err); } + if ( Etherpad.isContentEqual(revision.etherpadHtml, html) || (!revision.etherpadHtml && Etherpad.isContentEmpty(html)) @@ -899,14 +979,182 @@ const handlePublish = function(data, callback) { }; /** - * Join a collaborative document. - * Only users who have manager permissions on the collaborative document can join the pad. + * Publish a collaborative spreadsheet. When a sheet is published the following happens: + * + * - The HTML for this room is retrieved + * - A new revision is created with this HTML + * - The content object is updated with this HTML + * - The content item gets bumped to the top of all the libraries it resides in + * - An `updatedContent` event is fired so activities and PP images can be generated + * + * Note that this function does *NOT* perform any permission checks. It's assumed that this + * function deals with messages coming from RabbitMQ. Producers of those messages are expected + * to perform the necessary permissions checks. In the typical case where Ethercalc is submitting + * edit messages, the authorization happens by virtue of the app server constructing a session + * in Ethercalc. + * + * @param {Object} data The message as sent by Ethercalc + * @param {String} data.roomId The id for the Ethercalc room that has been published + * @param {String} data.userId The id of the user that published the document + * @param {Function} callback Standard callback function + * @param {Object} callback.err An error that occurred, if any + */ +const ethercalcPublish = function(data, callback) { + callback = + callback || + function(err) { + if (err) { + log().error({ err, data }, 'Error handling ethercalc edit'); + } + }; + + ContentDAO.Ethercalc.hasUserEditedSpreadsheet(data.contentId, data.userId, function(err, hasEdited) { + if (err) { + callback(err); + } + + if (!hasEdited) { + // No edits have been made + return callback(); + } + + log().trace({ data }, 'Got an ethercalc edit'); + ContentDAO.Content.getContent(data.contentId, function(err, contentObj) { + if (err) { + return callback(err); + } + + const roomId = contentObj.ethercalcRoomId; + PrincipalsDAO.getPrincipal(data.userId, function(err, principal) { + if (err) { + return callback(err); + } + + const ctx = Context.fromUser(principal); + Ethercalc.getHTML(roomId, function(err, html) { + if (err) { + return callback(err); + } + + // Get the latest OAE revision and compare the html to what's in Ethercalc. + // We only need to create a new revision if there is an actual update + ContentDAO.Revisions.getRevision(contentObj.latestRevisionId, function(err, revision) { + if (err) { + return callback(err); + } + + if ( + Ethercalc.isContentEqual(revision.ethercalcHtml, html) || + (!revision.ethercalcHtml && Ethercalc.isContentEmpty(html)) + ) { + // Spreadsheet has had edits but content has not changed - no further action necessary + emitter.emit(ContentConstants.events.EDITED_COLLABSHEET, ctx, contentObj); + + return callback(); + } + + // Otherwise we create a new revision + const newRevisionId = _generateRevisionId(contentObj.id); + Ethercalc.getRoom(roomId, function(err, snapshot) { + if (err) { + return callback(err); + } + + ContentDAO.Revisions.createRevision( + newRevisionId, + contentObj.id, + data.userId, + { + ethercalcHtml: html, + ethercalcSnapshot: snapshot + }, + function(err, revision) { + if (err) { + log().error( + { + err, + contentId: contentObj.id + }, + 'Could not create a revision for this collaborative spreadsheet' + ); + return callback({ + code: 500, + msg: 'Could not create a revision for this collaborative spreadsheet' + }); + } + + // eslint-disable-next-line no-unused-vars + Ethercalc.getRoom(roomId, function(err, snapshot) { + if (err) { + log().error( + { + err, + contentId: contentObj.id + }, + 'Failed to fetch Ethercalc snapshot for this collaborative spreadsheet' + ); + return callback({ + code: 500, + msg: 'Failed to fetch Ethercalc snapshot for this collaborative spreadsheet' + }); + } + + // Update the content so we can easily retrieve it. + // This will also bump the collabsheet to the top of the library lists + ContentDAO.Content.updateContent( + contentObj, + { + latestRevisionId: revision.revisionId + }, + true, + function(err, newContentObj) { + if (err) { + log().error( + { + err, + contentId: contentObj.id + }, + 'Could not update the main content this collaborative spreadsheet' + ); + return callback({ + code: 500, + msg: 'Could not update this collaborative spreadsheet' + }); + } + + // Add the revision on the content object so the UI doesn't have to do another request to get the HTML + newContentObj.latestRevision = revision; + // Emit an event for activities and preview processing + emitter.emit( + ContentConstants.events.UPDATED_CONTENT_BODY, + ctx, + newContentObj, + contentObj, + revision + ); + return callback(); + } + ); + }); + } + ); + }); + }); + }); + }); + }); + }); +}; + +/** + * Join a collaborative document or spreadsheet. + * Only users who have manager permissions on the collaborative document can join the pad/room * * @param {Context} ctx Standard context object containing the current user and the current tenant - * @param {String} contentId The ID of the collaborative document that should be joined + * @param {String} contentId The ID of the collaborative document or spreadsheet that should be joined * @param {Function} callback Standard callback function * @param {Object} callback.err An error that occurred, if any - * @param {Object} callback.url JSON object containing the url where the pad is accessible + * @param {Object} callback.url JSON object containing the url where the pad/room is accessible */ const joinCollabDoc = function(ctx, contentId, callback) { // Parameter validation @@ -921,24 +1169,33 @@ const joinCollabDoc = function(ctx, contentId, callback) { if (err) { return callback(err); } - if (contentObj.resourceSubType !== 'collabdoc') { - return callback({ code: 400, msg: 'This is not a collaborative document' }); - } - // Join the pad - Etherpad.joinPad(ctx, contentObj, (err, data) => { - if (err) { - return callback(err); - } + if (contentObj.resourceSubType === COLLABDOC) { + // Join the pad + Etherpad.joinPad(ctx, contentObj, (err, data) => { + if (err) { + return callback(err); + } - ContentDAO.Etherpad.saveAuthorId(data.author.authorID, ctx.user().id, err => { + ContentDAO.Etherpad.saveAuthorId(data.author.authorID, ctx.user().id, err => { + if (err) { + return callback(err); + } + + return callback(null, { url: data.url }); + }); + }); + } else if (contentObj.resourceSubType === COLLABSHEET) { + Ethercalc.joinRoom(ctx, contentObj, function(err, data) { if (err) { return callback(err); } - return callback(null, { url: data.url }); + callback(null, data); }); - }); + } else { + return callback({ code: 400, msg: 'This is not a collaborative document nor a spreadsheet' }); + } }); }; @@ -967,6 +1224,16 @@ const deleteContent = function(ctx, contentId, callback) { return callback(validator.getFirstError()); } + const done = (ctx, contentObj, members, callback) => { + emitter.emit(ContentConstants.events.DELETED_CONTENT, ctx, contentObj, members, function(err) { + if (err) { + return callback(_.first(err)); + } + + return callback(); + }); + }; + // Fist check whether or not the current user is a manager of the piece of content _canManage(ctx, contentId, (err, contentObj) => { if (err) { @@ -979,13 +1246,18 @@ const deleteContent = function(ctx, contentId, callback) { return callback(err); } - emitter.emit(ContentConstants.events.DELETED_CONTENT, ctx, contentObj, members, errs => { - if (errs) { - return callback(_.first(errs)); - } + if (contentObj.resourceSubType === COLLABSHEET) { + Ethercalc.deleteRoom(contentObj.ethercalcRoomId, function(err) { + if (err) { + return callback(err); + } - return callback(); - }); + log().info({ contentId }, 'Deleted an Ethercalc room'); + done(ctx, contentObj, members, callback); + }); + } else { + done(ctx, contentObj, members, callback); + } }); }); }; @@ -1024,6 +1296,7 @@ const shareContent = function(ctx, contentId, principalIds, callback) { if (err) { return callback(err); } + if (_.isEmpty(memberChangeInfo.changes)) { return callback(); } @@ -1117,11 +1390,12 @@ const setContentPermissions = function(ctx, contentId, changes, callback) { return callback(err); } - // Ensure all roles applied are valid. Editor is only valid for collabdocs + // Ensure all roles applied are valid. Editor is only valid for collabdocs and collabsheets const validRoles = [AuthzConstants.role.VIEWER, AuthzConstants.role.MANAGER]; - if (content.resourceSubType === 'collabdoc') { + if (content.resourceSubType === COLLABDOC || content.resourceSubType === COLLABSHEET) { validRoles.push(AuthzConstants.role.EDITOR); } + // eslint-disable-next-line no-unused-vars _.each(changes, (role, principalId) => { if (role !== false) { @@ -1141,6 +1415,7 @@ const setContentPermissions = function(ctx, contentId, changes, callback) { if (err) { return callback(err); } + if (_.isEmpty(memberChangeInfo.changes)) { return callback(); } @@ -1251,6 +1526,7 @@ const getContentMembersLibrary = function(ctx, contentId, start, limit, callback if (err) { return callback(err); } + if (!hasAccess) { return callback({ code: 401, @@ -1263,6 +1539,7 @@ const getContentMembersLibrary = function(ctx, contentId, start, limit, callback if (err) { return callback(err); } + if (_.isEmpty(memberIds)) { return callback(null, [], nextToken); } @@ -1289,6 +1566,7 @@ const getContentMembersLibrary = function(ctx, contentId, start, limit, callback role: memberRole }; } + return null; }) .compact() @@ -1406,6 +1684,7 @@ const _updateFileBody = function(ctx, contentId, file, callback) { validator.check(file.size, { code: 400, msg: 'Invalid size on the file object.' }).min(0); validator.check(file.name, { code: 400, msg: 'Missing name on the file object.' }).notEmpty(); } + if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -1414,6 +1693,7 @@ const _updateFileBody = function(ctx, contentId, file, callback) { if (err) { return callback(err); } + if (contentObj.resourceSubType !== 'file') { return callback({ code: 400, msg: 'This content object is not a file.' }); } @@ -1595,6 +1875,7 @@ const setPreviewItems = function( called = true; return cleanUpCallback(err); } + if (todo === 0 && !called) { // All files have been stored, store the metadata and exit called = true; @@ -1686,6 +1967,7 @@ const getSignedPreviewDownloadInfo = function(ctx, contentId, revisionId, previe if (validator.hasErrors()) { return callback(validator.getFirstError()); } + if (!Signature.verifyExpiringResourceSignature(ctx, contentId, signatureData.expires, signatureData.signature)) { return callback({ code: 401, msg: 'Invalid content signature data for accessing previews' }); } @@ -1787,6 +2069,7 @@ const updateContentMetadata = function(ctx, contentId, profileFields, callback) validator.check(profileFields.link, { code: 400, msg: 'A valid link should be provided' }).isUrl(); } } + if (profileFields.visibility) { validator .check(profileFields.visibility, { @@ -1795,6 +2078,7 @@ const updateContentMetadata = function(ctx, contentId, profileFields, callback) }) .isIn(_.values(AuthzConstants.visibility)); } + validator .check(null, { code: 401, msg: 'You have to be logged in to be able to update a content item' }) .isLoggedInUser(ctx); @@ -1812,6 +2096,7 @@ const updateContentMetadata = function(ctx, contentId, profileFields, callback) if (oldContentObj.resourceSubType !== 'link') { return callback({ code: 400, msg: 'This piece of content is not a link' }); } + if (profileFields.link !== oldContentObj.link) { // Reset the previews object so we don't show the old preview items while the new link is still being processed profileFields.previews = { status: ContentConstants.previews.PENDING }; @@ -1990,6 +2275,7 @@ const deleteComment = function(ctx, contentId, commentCreatedDate, callback) { if (err) { return callback(err); } + if (_.isEmpty(messages) || !messages[0]) { return callback({ code: 404, msg: 'The specified comment does not exist' }); } @@ -2041,6 +2327,7 @@ const _deleteComment = function(ctx, content, commentToDelete, callback) { // If a soft-delete occurred, we want to inform the consumer of the soft-delete message model return callback(null, deletedComment); } + return callback(); } ); @@ -2086,6 +2373,7 @@ const getContentLibraryItems = function(ctx, principalId, start, limit, callback if (err) { return callback(err); } + if (!hasAccess) { return callback({ code: 401, msg: 'You do not have access to this library' }); } @@ -2237,6 +2525,7 @@ const getRevision = function(ctx, contentId, revisionId, callback) { if (revisionId) { validator.check(revisionId, { code: 400, msg: 'A valid revisionId must be provided' }).isResourceId(); } + if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -2271,6 +2560,7 @@ const getRevisionDownloadInfo = function(ctx, contentId, revisionId, callback) { if (revisionId) { validator.check(revisionId, { code: 400, msg: 'A valid revisionId must be provided' }).isResourceId(); } + if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -2280,6 +2570,7 @@ const getRevisionDownloadInfo = function(ctx, contentId, revisionId, callback) { if (err) { return callback(err); } + if (content.resourceSubType !== 'file') { return callback({ code: 400, msg: 'Only file content items can be downloaded' }); } @@ -2298,6 +2589,7 @@ const getRevisionDownloadInfo = function(ctx, contentId, revisionId, callback) { if (err) { return callback(err); } + if (content.id !== revision.contentId) { // It's possible that the user specified a revision id that belonged to a different content item. Yikes! return callback({ @@ -2340,6 +2632,7 @@ const _getRevision = function(ctx, contentObj, revisionId, callback) { // Ex: Alice has access to c:cam:aliceDoc but not to c:cam:bobDoc which has revision rev:cam:foo // doing getRevision(ctx, 'c:cam:aliceDoc', 'rev:cam:foo', ..) should return this error. } + if (revisionObj.contentId !== contentObj.id) { return callback({ code: 400, @@ -2356,6 +2649,7 @@ const _getRevision = function(ctx, contentObj, revisionId, callback) { if (err) { return callback(err); } + if (_.isEmpty(revisions)) { return callback({ code: 404, msg: 'No revision found for ' + contentObj.id }); } @@ -2417,6 +2711,7 @@ const restoreRevision = function(ctx, contentId, revisionId, callback) { if (err) { return callback(err); } + if (contentObj.id !== revision.contentId) { return callback({ code: 400, @@ -2432,10 +2727,10 @@ const restoreRevision = function(ctx, contentId, revisionId, callback) { } /*! - * We need to update the content item in the Content CF. - * We do so by copying all the non-standard fields from the revision - * to the Content CF. - */ + * We need to update the content item in the Content CF. + * We do so by copying all the non-standard fields from the revision + * to the Content CF. + */ const blacklist = [ 'revisionId', 'contentId', @@ -2463,7 +2758,7 @@ const restoreRevision = function(ctx, contentId, revisionId, callback) { // If this piece of content is a collaborative document, // we need to set the text in etherpad. - if (contentObj.resourceSubType === 'collabdoc') { + if (contentObj.resourceSubType === COLLABDOC) { Etherpad.setHTML(contentObj.id, contentObj.etherpadPadId, revision.etherpadHtml, err => { if (err) { return callback(err); @@ -2523,6 +2818,7 @@ const _storePreview = function(ctx, previewReference, options, callback) { // to reference it return callback(null, 'remote:' + previewReference); } + // Otherwise, it is expected to be a stream reference to a file on disk, in which case we want to store it // using the tenant default storage mechanism ContentUtil.getStorageBackend(ctx).store(ctx.tenant().alias, previewReference, options, callback); @@ -2593,6 +2889,7 @@ module.exports = { getFullContentProfile, createLink, createFile, + createCollabSheet, createCollabDoc, handlePublish, joinCollabDoc, @@ -2617,5 +2914,6 @@ module.exports = { getRevisionDownloadInfo, restoreRevision, verifySignedDownloadQueryString, + ethercalcPublish, emitter }; diff --git a/packages/oae-content/lib/constants.js b/packages/oae-content/lib/constants.js index 71cc5bb354..a98874e0d8 100644 --- a/packages/oae-content/lib/constants.js +++ b/packages/oae-content/lib/constants.js @@ -22,14 +22,10 @@ ContentConstants.role = { // Determines not only all known roles, but the ordered priority they take as the "effective" // role. (e.g., if you are both a viewer and a manager, your effective role is "manager", so it // must be later in the list) - ALL_PRIORITY: [ - AuthzConstants.role.VIEWER, - AuthzConstants.role.EDITOR, - AuthzConstants.role.MANAGER - ] + ALL_PRIORITY: [AuthzConstants.role.VIEWER, AuthzConstants.role.EDITOR, AuthzConstants.role.MANAGER] }; -ContentConstants.resourceSubTypes = ['collabdoc', 'link', 'file']; +ContentConstants.resourceSubTypes = ['collabdoc', 'collabsheet', 'link', 'file']; ContentConstants.events = { CREATED_COMMENT: 'createdComment', @@ -40,6 +36,7 @@ ContentConstants.events = { RESTORED_REVISION: 'restoredContent', UPDATED_CONTENT: 'updatedContent', EDITED_COLLABDOC: 'editedCollabdoc', + EDITED_COLLABSHEET: 'editedCollabsheet', GET_CONTENT_LIBRARY: 'getContentLibrary', GET_CONTENT_PROFILE: 'getContentProfile', UPDATED_CONTENT_BODY: 'updatedContentBody', @@ -91,5 +88,7 @@ ContentConstants.search = { }; ContentConstants.queue = { - ETHERPAD_PUBLISH: 'oae-content/etherpad-publish' + ETHERPAD_PUBLISH: 'oae-content/etherpad-publish', + ETHERCALC_PUBLISH: 'oae-content/ethercalc-publish', + ETHERCALC_EDIT: 'oae-content/ethercalc-edit' }; diff --git a/packages/oae-content/lib/init.js b/packages/oae-content/lib/init.js index 3557c8257b..16cbc172a1 100644 --- a/packages/oae-content/lib/init.js +++ b/packages/oae-content/lib/init.js @@ -23,6 +23,7 @@ const ContentAPI = require('./api'); const { ContentConstants } = require('./constants'); const ContentSearch = require('./search'); const Etherpad = require('./internal/etherpad'); +const Ethercalc = require('./internal/ethercalc'); const LocalStorage = require('./backends/local'); module.exports = function(config, callback) { @@ -45,6 +46,9 @@ module.exports = function(config, callback) { // Initialize the etherpad client. Etherpad.refreshConfiguration(config.etherpad); + // Initialize the ethercalc client + Ethercalc.refreshConfiguration(config.ethercalc); + ContentSearch.init(err => { if (err) { return callback(err); @@ -75,7 +79,30 @@ module.exports = function(config, callback) { return callback(err); } - return callback(); + // Same for Ethercalc - no ack because ack breaks Ethercalc + TaskQueue.bind( + ContentConstants.queue.ETHERCALC_EDIT, + Ethercalc.setEditedBy, + { subscribe: { ack: false } }, + function(err) { + if (err) { + return callback(err); + } + + TaskQueue.bind( + ContentConstants.queue.ETHERCALC_PUBLISH, + ContentAPI.ethercalcPublish, + { subscribe: { ack: false } }, + function(err) { + if (err) { + return callback(err); + } + + return callback(); + } + ); + } + ); }); }); }); diff --git a/packages/oae-content/lib/internal/dao.content.js b/packages/oae-content/lib/internal/dao.content.js index f7a7d4b00d..908404abf1 100644 --- a/packages/oae-content/lib/internal/dao.content.js +++ b/packages/oae-content/lib/internal/dao.content.js @@ -27,6 +27,9 @@ const { Content } = require('oae-content/lib/model'); const { ContentConstants } = require('oae-content/lib/constants'); const RevisionsDAO = require('./dao.revisions'); +const COLLABDOC = 'collabdoc'; +const COLLABSHEET = 'collabsheet'; + /// //////////// // Retrieval // /// //////////// @@ -44,6 +47,7 @@ const getContent = function(contentId, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback({ code: 404, msg: "Couldn't find content: " + contentId }, null); } @@ -126,7 +130,7 @@ const getAllContentMembers = function(contentId, callback) { * @param {String} contentId The id of the piece of content * @param {String} revisionId The id of the first revision * @param {String} createdBy The id of the user who is creating the content item - * @param {String} resourceSubType The content type. Possible values are "file", "collabdoc" and "link" + * @param {String} resourceSubType The content type. Possible values are "file", "collabdoc", "collabsheet" and "link" * @param {String} displayName The display name for the piece of content * @param {String} description The description of the piece of content [optional] * @param {String} visibility The visibility setting for the piece of content. Possible values are "public", "loggedin" and "private" [optional] @@ -232,17 +236,18 @@ const updateContent = function(contentObj, profileUpdates, librariesUpdate, call // In case the revision ID has changed if (newContentObj.resourceSubType === 'file') { - newContentObj.downloadPath = - '/api/content/' + newContentObj.id + '/download/' + newContentObj.latestRevisionId; + newContentObj.downloadPath = '/api/content/' + newContentObj.id + '/download/' + newContentObj.latestRevisionId; } if (!librariesUpdate) { return callback(null, newContentObj); } + _updateContentLibraries(newContentObj, oldLastModified, newContentObj.lastModified, [], err => { if (err) { return callback(err); } + callback(null, newContentObj); }); }); @@ -267,6 +272,7 @@ const deleteContent = function(contentObj, callback) { if (err) { return callback(err); } + if (_.isEmpty(members)) { return callback(null, []); } @@ -289,25 +295,21 @@ const deleteContent = function(contentObj, callback) { } // Simply remove this item from all member libraries - LibraryAPI.Index.remove( - ContentConstants.library.CONTENT_LIBRARY_INDEX_NAME, - libraryRemoveEntries, - err => { - if (err) { - // If there was an error updating libraries here, the permissions were still changed, so we should not return an error. Just log it. - log().warn( - { - err, - contentObj, - libraryRemoveEntries - }, - 'Failed to update user libraries after updating content permissions.' - ); - } - - return callback(null, members); + LibraryAPI.Index.remove(ContentConstants.library.CONTENT_LIBRARY_INDEX_NAME, libraryRemoveEntries, err => { + if (err) { + // If there was an error updating libraries here, the permissions were still changed, so we should not return an error. Just log it. + log().warn( + { + err, + contentObj, + libraryRemoveEntries + }, + 'Failed to update user libraries after updating content permissions.' + ); } - ); + + return callback(null, members); + }); }); }); }); @@ -381,23 +383,16 @@ const iterateAll = function(properties, batchSize, onEach, callback) { } /*! - * Handles each batch from the cassandra iterateAll method. - * - * @see Cassandra#iterateAll - */ + * Handles each batch from the cassandra iterateAll method. + * + * @see Cassandra#iterateAll + */ const _iterateAllOnEach = function(rows, done) { // Convert the rows to a hash and delegate action to the caller onEach method return onEach(_.map(rows, Cassandra.rowToHash), done); }; - Cassandra.iterateAll( - properties, - 'Content', - 'contentId', - { batchSize }, - _iterateAllOnEach, - callback - ); + Cassandra.iterateAll(properties, 'Content', 'contentId', { batchSize }, _iterateAllOnEach, callback); }; /** @@ -422,19 +417,13 @@ const updateContentLibraries = function(contentObj, removedMembers, callback) { } // Update the libraries. - _updateContentLibraries( - contentObj, - oldLastModified, - newContentObj.lastModified, - removedMembers, - err => { - if (err) { - return callback(err, newContentObj); - } - - return callback(null, newContentObj); + _updateContentLibraries(contentObj, oldLastModified, newContentObj.lastModified, removedMembers, err => { + if (err) { + return callback(err, newContentObj); } - ); + + return callback(null, newContentObj); + }); }); }; @@ -448,13 +437,7 @@ const updateContentLibraries = function(contentObj, removedMembers, callback) { * @param {Function} [callback] Standard callback function * @param {Object} [callback.err] Error object containing the error message */ -const _updateContentLibraries = function( - contentObj, - oldLastModified, - newLastModified, - removedMembers, - callback -) { +const _updateContentLibraries = function(contentObj, oldLastModified, newLastModified, removedMembers, callback) { // Grab all the current members. getAllContentMembers(contentObj.id, (err, members) => { if (err) { @@ -474,11 +457,7 @@ const _updateContentLibraries = function( resource: contentObj }; }); - return LibraryAPI.Index.insert( - ContentConstants.library.CONTENT_LIBRARY_INDEX_NAME, - insertEntries, - callback - ); + return LibraryAPI.Index.insert(ContentConstants.library.CONTENT_LIBRARY_INDEX_NAME, insertEntries, callback); } // Collect any library index update operations from the member ids that are not removal @@ -564,9 +543,11 @@ const _rowToContent = function(row) { contentObj.mime = hash.mime; } else if (contentObj.resourceSubType === 'link') { contentObj.link = hash.link; - } else if (contentObj.resourceSubType === 'collabdoc') { + } else if (contentObj.resourceSubType === COLLABDOC) { contentObj.etherpadGroupId = hash.etherpadGroupId; contentObj.etherpadPadId = hash.etherpadPadId; + } else if (contentObj.resourceSubType === COLLABSHEET) { + contentObj.ethercalcRoomId = hash.ethercalcRoomId; } return contentObj; diff --git a/packages/oae-content/lib/internal/dao.ethercalc.js b/packages/oae-content/lib/internal/dao.ethercalc.js new file mode 100644 index 0000000000..5a7cbecc12 --- /dev/null +++ b/packages/oae-content/lib/internal/dao.ethercalc.js @@ -0,0 +1,118 @@ +/*! + * Copyright 2018 Apereo Foundation (AF) Licensed under the + * Educational Community License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://opensource.org/licenses/ECL-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +const _ = require('underscore'); +const Redis = require('oae-util/lib/redis'); +const log = require('oae-logger').logger('oae-ethercalc'); + +/** + * Check whether a particular user has edited an Ethercalc spreadsheet + * + * @param {String} contentId The ID of the OAE content item that may have been edited + * @param {String} userId The ID of the OAE user who may have edited the room + * @param {Function} callback Standard callback function + * @param {Object} callback.err An error that occurred, if any + * @param {Boolean} callback.edit Has this OAE user made edits to this content item? + */ +const hasUserEditedSpreadsheet = function(contentId, userId, callback) { + const key = _getEditMappingKey(contentId); + const client = Redis.getClient(); + client.exists(key, function(err, exists) { + if (err) { + log().error({ err, contentId, userId }, 'Failed to check whether user has edited Ethercalc spreadsheet'); + return callback({ + code: 500, + msg: 'Failed to check whether user has edited Ethercalc spreadsheet' + }); + } + + if (exists) { + client.lrange(key, 0, -1, function(err, replies) { + if (err) { + log().error({ err, contentId, userId }, 'Failed to fetch editors for Ethercalc spreadsheet'); + return callback({ + code: 500, + msg: 'Failed to fetch editors for Ethercalc spreadsheet' + }); + } + + if (_.contains(replies, userId)) { + // Let's take out the references to this user's edits since we're sending out a notification + client.lrem(key, 0, userId, function(err) { + if (err) { + log().error( + { + err, + contentId, + userId + }, + 'Failed purge cache of user edits to Ethercalc spreadsheet' + ); + return callback({ + code: 500, + msg: 'Failed purge cache of user edits to Ethercalc spreadsheet' + }); + } + + // This user has edited the document + return callback(null, true); + }); + } + + // There are edits, but not from this user + return callback(null, false); + }); + } + + // There are no edits recorded for this document + return callback(null, false); + }); +}; + +/** + * Store information about which user edited an Ethercalc spreadsheet + * + * @param {String} contentId The ID of the OAE content item that was edited + * @param {String} userId The ID of the OAE user who edited the room + * @param {Function} callback Standard callback function + * @param {Object} callback.err An error that occurred, if any + */ +const setEditedBy = function(contentId, userId, callback) { + const key = _getEditMappingKey(contentId); + Redis.getClient().rpush(key, userId, function(err) { + if (err) { + log().error({ err, contentId, userId }, 'Failed to store Ethercalc user edits'); + return callback({ + code: 500, + msg: 'Failed to store Ethercalc user edits' + }); + } + + return callback(); + }); +}; + +/** + * Get the Redis key used to map OAE user IDs to editors of content item + + * @param {String} contentId The ID of the OAE content item that was edited + * @return {String} The Redis key used to map the Ethercalc author to the corresponding OAE user ID + * @api private + */ +const _getEditMappingKey = function(contentId) { + return `ethercalc:edits:${contentId}`; +}; + +module.exports = { hasUserEditedSpreadsheet, setEditedBy }; diff --git a/packages/oae-content/lib/internal/dao.js b/packages/oae-content/lib/internal/dao.js index ad84746dab..152eeedc20 100644 --- a/packages/oae-content/lib/internal/dao.js +++ b/packages/oae-content/lib/internal/dao.js @@ -15,5 +15,6 @@ module.exports.Content = require('./dao.content'); module.exports.Etherpad = require('./dao.etherpad'); +module.exports.Ethercalc = require('./dao.ethercalc'); module.exports.Previews = require('./dao.previews'); module.exports.Revisions = require('./dao.revisions'); diff --git a/packages/oae-content/lib/internal/ethercalc.js b/packages/oae-content/lib/internal/ethercalc.js new file mode 100644 index 0000000000..845515d790 --- /dev/null +++ b/packages/oae-content/lib/internal/ethercalc.js @@ -0,0 +1,360 @@ +/*! + * Copyright 2018 Apereo Foundation (AF) Licensed under the + * Educational Community License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://opensource.org/licenses/ECL-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +const url = require('url'); +const _ = require('underscore'); +const cheerio = require('cheerio'); +const Ethercalc = require('ethercalc-client'); + +const log = require('oae-logger').logger('ethercalc'); + +const ContentDAO = require('./dao'); + +let ethercalcConfig = null; +let ethercalc = null; + +const SOCIAL_CALC_FORMAT_BEGIN_LINE = 'socialcalc:version:1.0'; +const SOCIAL_CALC_FORMAT_END_LINE = '--SocialCalcSpreadsheetControlSave--'; +const TABLE_ELEMENT = 'table'; + +/** + * Refresh the runtime ethercalc configuration (host, port, etc...) with the one provided. More + * documentation about the ethercalc configuration may be found in the `config.ethercalc` key of the + * default config.js file. + * + * @param {Object} _ethercalcConfig The ethercalc config from config.js + */ +const refreshConfiguration = function(_ethercalcConfig) { + // Remember this config. + ethercalcConfig = _ethercalcConfig; + ethercalc = new Ethercalc(ethercalcConfig.host, ethercalcConfig.port, ethercalcConfig.protocol); +}; + +/** + * Get the Ethercalc configuration. + * + * @return {Object} The Ethercalc configuration. + */ +const getConfig = function() { + return ethercalcConfig; +}; + +/** + * Creates a new spreadsheet via the Ethercalc API. + * + * @param {Object} content An object containing the data for an Ethercalc room + * @param {Function} callback Standard callback function + * @param {Object} callback.err An error that occurred, if any + * @param {Object} callback.snapshot A snapshot containing data for new Ethercalc room + */ +const createRoom = function(content, callback) { + const { contentId } = content; + let roomId = null; + log().trace({ contentId }, 'Creating Ethercalc room'); + ethercalc + .createRoom() + .then(data => { + // Ethercalc returns the relative path so strip out starting / + roomId = data.slice(1); + log().info({ contentId, ethercalcRoomId: roomId }, 'Created Ethercalc room'); + return callback(null, roomId); + }) + .catch(error => { + log().error( + { err: error, contentId, ethercalcRoomId: roomId, ethercalc: ethercalcConfig.host }, + 'Could not create Ethercalc room' + ); + return callback(error); + }); +}; + +/** + * Deletes an existing Ethercalc room via the Ethercalc API. + * + * @param {String} roomId The id of the Ethercalc room that should be deleted + * @param {Function} callback Standard callback function + * @param {Object} callback.err An error that occurred, if any + */ +const deleteRoom = function(roomId, callback) { + log().trace({ roomId, ethercalc: ethercalcConfig.host }, 'Deleting Ethercalc room'); + ethercalc + .deleteRoom(roomId) + .then(deleted => { + if (deleted) { + log().info('Deleted Ethercalc room'); + return callback(null); + } + + log().error( + { code: 500, msg: 'Encountered error while deleting Ethercalc room' }, + 'Encountered error while deleting Ethercalc room' + ); + return callback({ code: 500, msg: 'Could not delete Ethercalc room' }); + }) + .catch(error => { + log().error({ err: error, roomId, ethercalc: ethercalcConfig.host }, 'Could not delete Ethercalc room'); + return callback(error); + }); +}; + +/** + * Get the HTML for a room + * + * @param {String} roomId The id for which the HTML should be retrieved from ethercalc + * @param {Function} callback Standard callback function + * @param {Object} callback.err An error that occurred, if any + * @param {String} callback.html The HTML for this room + */ +const getHTML = function(roomId, callback) { + log().trace({ roomId, ethercalc: ethercalcConfig.host }, 'Getting Ethercalc room as HTML'); + ethercalc + .getHTML(roomId) + .then(html => { + if (!_isHtmlDocument(html)) { + log().error({ roomId, ethercalc: ethercalcConfig.host }, 'Ethercalc sheet contents are not valid HTML'); + return callback({ code: 500, msg: 'Ethercalc sheet contents are not valid HTML' }); + } + + return callback(null, html); + }) + .catch(error => { + log().error({ err: error, roomId, ethercalc: ethercalcConfig.host }, 'Could not grab the HTML from ethercalc'); + return callback({ code: 500, msg: 'Could not grab the HTML from ethercalc' }); + }); +}; + +/** + * Fetch an ethercalc room + * + * @param {String} roomId The content id for which the HTML should be retrieved from ethercalc + * @param {Function} callback Standard callback function + * @param {Object} callback.err An error that occurred, if any + * @param {String} callback.data This room in socialcalc format + */ +const getRoom = function(roomId, callback) { + log().trace({ roomId, ethercalc: ethercalcConfig.host }, 'Getting Ethercalc room in socialcalc format'); + ethercalc + .getRoom(roomId) + .then(data => { + if (!_isSCDocument(data)) { + log().error( + { roomId, ethercalc: ethercalcConfig.host }, + 'Ethercalc sheet contents are not in correct socialcalc format' + ); + return callback({ code: 500, msg: 'Ethercalc sheet contents are not in correct socialcalc format' }); + } + + log().trace({ roomId, ethercalc: ethercalcConfig.host }, 'Fetched ethercalc room'); + return callback(null, data); + }) + .catch(error => { + log().error( + { err: error, roomId, ethercalc: ethercalcConfig.host }, + 'Could not fetch Ethercalc room in socialcalc format' + ); + return callback({ code: 500, msg: 'Could not fetch Ethercalc room in socialcalc format' }); + }); +}; + +/** + * Set the contents for a room + * + * @param {String} roomId The content id for which the HTML should be set in ethercalc + * @param {String} snapshot The data that should be used for the ethercalc room in SC format + * @param {Function} callback Standard callback function + * @param {Object} callback.err An error that occurred, if any + */ +const setSheetContents = function(roomId, snapshot, callback) { + log().trace({ roomId, snapshot, ethercalc: ethercalcConfig.host }, 'Setting Ethercalc contents'); + + ethercalc + .overwrite(roomId, snapshot, 'socialcalc') + // eslint-disable-next-line no-unused-vars + .then(response => { + return callback(null); + }) + .catch(error => { + log().error( + { err: error, roomId, ethercalc: ethercalcConfig.host }, + 'Could not set sheet contents on the Ethercalc instance' + ); + return callback({ code: 500, msg: 'Could not set sheet contents on the Ethercalc instance' }); + }); +}; + +/** + * Joins the current user in an ethercalc room. + * This assumes that the current user has access to the collaborative spreadsheet. + * + * @param {Context} ctx Standard context object containing the current user and the current tenant + * @param {Content} contentObj The content object for the room that should be joined + * @param {Function} callback Standard callback function + * @param {Object} callback.err An error that occurred, if any + * @param {String} callback.url The URL that can be used to embed the room + */ +const joinRoom = function(ctx, contentObj, callback) { + const user = ctx.user(); + if (!user) { + return callback({ code: 401, msg: 'Anonymous users are not allowed to join collaborative spreadsheets' }); + } + + log().trace(`Joining Ethercalc room ${contentObj.ethercalcRoomId} as user ${user}`); + + // Get the language for the current user. + const language = user.locale ? _.first(user.locale.split('_')) : 'en'; + const url = getRoomUrl(contentObj, user.id, language); + return callback(null, { url }); +}; + +/** + * Get the URL where users can view the ethercalc room. + * This can be used to embed in the page via an iframe. + * The URL will be of the form: + * http://:/?contentId=&displayName=&authorId=&language=en + * + * @param {Content} contentObj The content object for the room that should be joined + * @param {String} userId The ID of the user in OAE + * @param {String} [language] The 2 character string that identifies the user's prefered language + * @return {String} The URL to the room that can be used to embed in a page + */ +// eslint-disable-next-line no-unused-vars +const getRoomUrl = function(contentObj, userId, language) { + return url.format({ + protocol: ethercalcConfig.protocol, + hostname: ethercalcConfig.host, + port: ethercalcConfig.port, + pathname: contentObj.ethercalcRoomId, + query: { + author: userId, + content: contentObj.id + } + }); +}; + +/** + * Determine if the given sheet is empty. Works with both socialcalc and HTML formats. + * + * @param {String} content The content of the ethercalc spreadsheet + * @return {Boolean} Whether or not the content is considered empty + */ +const isContentEmpty = function(content) { + if (!content) { + return true; + } + + if (_isHtmlDocument(content)) { + // Empty sheets only have a single cell + if (content.match(/cell_[\w][\d]/g).length === 1) { + // Make sure that cell is empty + const $ = cheerio.load(content); + return $('#cell_A1').text(); + } + + return false; + } + + // Check for existing cell values in social calc format. Cells are in format: `cell:A1:t:test` + return content.test(/\bcell\:\w\d/g); +}; + +/** + * Determine if one set of ethercalc HTML content is equal to another. + * + * @param {String} one Content of one ethercalc document + * @param {String} other Content of another ethercalc document + * @return {Boolean} Whether or not the content is equivalent to eachother + */ +const isContentEqual = function(one, other) { + if (one === other) { + return true; + } + + if (!one || !other) { + return false; + } + + const $one = cheerio.load(one); + const $other = cheerio.load(other); + return $one(TABLE_ELEMENT).html() === $other(TABLE_ELEMENT).html(); +}; + +/** + * Record which user has edited an Ethercalc room + * + * @param {Object} data An object containing the OAE user ID and content ID + * @param {Function} callback Standard callback function + * @param {Object} callback.err An error that occurred, if any + * @api private + */ +const setEditedBy = function(data, callback) { + if (data.contentId && data.userId) { + ContentDAO.Ethercalc.setEditedBy(data.contentId, data.userId, function(err) { + if (err) { + return callback(err); + } + + return callback(null); + }); + } +}; + +/** + * Determine if the given content is a valid HTML table + * + * @param {String} content The content to check + * @return {Boolean} Whether or not the content is a valid HTML spreadsheet + * @api private + */ +const _isHtmlDocument = function(content) { + if (!content) { + return false; + } + + const $ = cheerio.load(content); + return $(TABLE_ELEMENT).length > 0; +}; + +/** + * Determine if the given content is in valid socialcalc format + * + * @param {String} content The content to check + * @return {Boolean} Whether or not the content is valid socialcalc + * @api private + */ +const _isSCDocument = function(content) { + if (!content) { + return false; + } + + content = content.trim(); + + // FIXME This isn't ideal, consider replacing with regexp which is also not ideal + return content.startsWith(SOCIAL_CALC_FORMAT_BEGIN_LINE) && content.endsWith(SOCIAL_CALC_FORMAT_END_LINE); +}; + +module.exports = { + refreshConfiguration, + getConfig, + createRoom, + deleteRoom, + getHTML, + getRoom, + setSheetContents, + joinRoom, + getRoomUrl, + isContentEmpty, + isContentEqual, + setEditedBy +}; diff --git a/packages/oae-content/lib/migration.js b/packages/oae-content/lib/migration.js index f5aa5a7a86..b98918f986 100644 --- a/packages/oae-content/lib/migration.js +++ b/packages/oae-content/lib/migration.js @@ -1,3 +1,4 @@ +const async = require('async'); const Cassandra = require('oae-util/lib/cassandra'); /** @@ -19,7 +20,22 @@ const ensureSchema = function(callback) { RevisionByContent: 'CREATE TABLE "RevisionByContent" ("contentId" text, "created" text, "revisionId" text, PRIMARY KEY ("contentId", "created")) WITH COMPACT STORAGE' }, - callback + () => { + const queries = [ + { cql: 'ALTER TABLE "Content" ADD "ethercalcRoomId" text;', parameters: [] }, + { cql: 'ALTER TABLE "Revisions" ADD "ethercalcSnapshot" text;', parameters: [] }, + { cql: 'ALTER TABLE "Revisions" ADD "ethercalcHtml" text;', parameters: [] } + ]; + async.eachSeries( + queries, + (eachQuery, done) => { + Cassandra.runQuery(eachQuery.cql, eachQuery.parameters, done); + }, + () => { + callback(); + } + ); + } ); }; diff --git a/packages/oae-content/lib/model.js b/packages/oae-content/lib/model.js index 93d607dcc9..33530a0eb1 100644 --- a/packages/oae-content/lib/model.js +++ b/packages/oae-content/lib/model.js @@ -31,7 +31,7 @@ const TenantsAPI = require('oae-tenants'); * @param {String} visibility The visibility of the content item. One of `public`, `loggedin`, `private` * @param {String} displayName The display name of the content item * @param {String} description A longer description for the content item - * @param {String} resourceSubType The content item type. One of `file`, `collabdoc`, `link` + * @param {String} resourceSubType The content item type. One of `file`, `collabdoc`, `collabsheet`, `link` * @param {String} createdBy The id of the user who created the content item * @param {Number} created The timestamp (millis since epoch) at which the content item was created * @param {Number} lastModified The timestamp (millis since epoch) at which the content item was last modified @@ -52,7 +52,7 @@ const Content = function( previews ) { const that = {}; - const {resourceId} = AuthzUtil.getResourceFromId(id); + const { resourceId } = AuthzUtil.getResourceFromId(id); that.tenant = TenantsAPI.getTenant(tenantAlias).compact(); that.id = id; diff --git a/packages/oae-content/lib/rest.js b/packages/oae-content/lib/rest.js index ff71e38b2a..a2b2dde4b1 100644 --- a/packages/oae-content/lib/rest.js +++ b/packages/oae-content/lib/rest.js @@ -23,6 +23,9 @@ const OaeUtil = require('oae-util/lib/util'); const ContentAPI = require('./api'); const { ContentConstants } = require('./constants'); +const COLLABDOC = 'collabdoc'; +const COLLABSHEET = 'collabsheet'; + /** * Verify the signature information provided by a signed download request and * pass it on to the download handler to complete the download request @@ -70,6 +73,35 @@ const _handleSignedDownload = function(req, res) { * @HttpResponse 401 You have to be logged in to be able to create a content item */ +/** + * @REST postContentCreateCollabsheet + * + * Create a new collaborative spreadsheet + * + * @Server tenant + * @Method POST + * @Path /content/create + * @FormParam {string} displayName The display name of the collaborative spreadsheet + * @FormParam {string} resourceSubType The content item type [spreadsheet] + * @FormParam {string} [description] A longer description for the collaborative spreadsheet + * @FormParam {string[]} [managers] Unique identifier(s) for users and groups to add as managers of the collaborative spreadsheet. The user creating the collaborative document will be added as a manager automatically + * @FormParam {string[]} [editors] Unique identifier(s) for users and groups to add as editors of the collaborative spreadsheet + * @FormParam {string[]} [viewers] Unique identifier(s) for users and groups to add as members of the collaborative spreadsheet + * @FormParam {string[]} [folders] Unique identifier(s) for folders to which the collaborative spreadsheet should be added + * @FormParam {string} [visibility] The visibility of the collaborative spreadsheet. Defaults to the configured tenant default [loggedin,private,public] + * @Return {BasicContent} The created collaborative spreadsheet + * @HttpResponse 201 Spreadsheet created + * @HttpResponse 400 A display name must be provided + * @HttpResponse 400 A display name can be at most 1000 characters long + * @HttpResponse 400 A description can only be 10000 characters long + * @HttpResponse 400 A valid resourceSubType must be provided. This can be "file", "collabdoc", "collabsheet" or "link" + * @HttpResponse 400 An invalid content visibility option has been provided. This can be "private", "loggedin" or "public" + * @HttpResponse 400 One or more target members being granted access are not authorized to become members on this content item + * @HttpResponse 400 One or more target members being granted access do not exist + * @HttpResponse 400 The additional members should be specified as an object + * @HttpResponse 401 You have to be logged in to be able to create a content item + */ + /** * @REST postContentCreateFile * @@ -91,7 +123,7 @@ const _handleSignedDownload = function(req, res) { * @HttpResponse 400 A display name must be provided * @HttpResponse 400 A display name can be at most 1000 characters long * @HttpResponse 400 A description can only be 10000 characters long - * @HttpResponse 400 A valid resourceSubType must be provided. This can be "file", "collabdoc" or "link" + * @HttpResponse 400 A valid resourceSubType must be provided. This can be "file", "collabdoc", "collabsheet" or "link" * @HttpResponse 400 An invalid content visibility option has been provided. This can be "private", "loggedin" or "public" * @HttpResponse 400 One or more target members being granted access are not authorized to become members on this content item * @HttpResponse 400 One or more target members being granted access do not exist @@ -122,7 +154,7 @@ const _handleSignedDownload = function(req, res) { * @HttpResponse 400 A display name can be at most 1000 characters long * @HttpResponse 400 A description can only be 10000 characters long * @HttpResponse 400 A valid link must be provided - * @HttpResponse 400 A valid resourceSubType must be provided. This can be "file", "collabdoc" or "link" + * @HttpResponse 400 A valid resourceSubType must be provided. This can be "file", "collabdoc", "collabsheet" or "link" * @HttpResponse 400 An invalid content visibility option has been provided. This can be "private", "loggedin" or "public" * @HttpResponse 400 One or more target members being granted access are not authorized to become members on this content item * @HttpResponse 400 One or more target members being granted access do not exist @@ -152,6 +184,7 @@ OAE.tenantRouter.on('post', '/api/content/create', (req, res) => { if (req.files && req.files.file) { uploadedFile = req.files.file; } + _createContent( req.ctx, req.body.resourceSubType, @@ -190,7 +223,7 @@ OAE.tenantRouter.on('post', '/api/content/create', (req, res) => { * @param {String} [link] The URL when creating a content item of resourceSubType `link` * @param {File} [uploadedFile] The file object when creating a content item of resourceSubType `file` * @param {String[]} folderIds The ids of folders where the content item should be added to - * @param {Object} additionalMembers Object where the keys represent principal ids that need to be added to the content upon creation and the values represent the role that principal will have. Possible values are "viewer" and "manager", as well as "editor" for collabdocs + * @param {Object} additionalMembers Object where the keys represent principal ids that need to be added to the content upon creation and the values represent the role that principal will have. Possible values are "viewer" and "manager", as well as "editor" for collabdocs or collabsheets * @param {Function} callback Standard callback function * @param {Object} callback.err An error object, if any * @param {Content} callback.content The created content object @@ -223,6 +256,7 @@ const _createContent = function( // File creation } + if (resourceSubType === 'file') { return ContentAPI.createFile( ctx, @@ -237,7 +271,8 @@ const _createContent = function( // Collaborative document creation } - if (resourceSubType === 'collabdoc') { + + if (resourceSubType === COLLABDOC) { return ContentAPI.createCollabDoc( ctx, displayName, @@ -250,9 +285,23 @@ const _createContent = function( // Not a recognized file type } + + // Collaborative spreadsheet creation + if (resourceSubType === COLLABSHEET) { + return ContentAPI.createCollabSheet( + ctx, + displayName, + description, + visibility, + additionalMembers, + folderIds, + callback + ); + } + return callback({ code: 400, - msg: 'Unrecognized resourceSubType. Accepted values are "link", "file" and "collabdoc"' + msg: 'Unrecognized resourceSubType. Accepted values are "link", "file", "collabdoc" and "collabsheet"' }); }; @@ -276,6 +325,7 @@ OAE.tenantRouter.on('delete', '/api/content/:contentId', (req, res) => { if (err) { return res.status(err.code).send(err.msg); } + res.status(200).end(); }); }); @@ -298,6 +348,7 @@ OAE.tenantRouter.on('get', '/api/content/:contentId', (req, res) => { if (err) { return res.status(err.code).send(err.msg); } + res.status(200).send(contentProfile); }); }); @@ -330,17 +381,13 @@ OAE.tenantRouter.on('get', '/api/content/:contentId', (req, res) => { * @HttpResponse 401 You have to be logged in to be able to update a content item */ OAE.tenantRouter.on('post', '/api/content/:contentId', (req, res) => { - ContentAPI.updateContentMetadata( - req.ctx, - req.params.contentId, - req.body, - (err, newContentObj) => { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(200).send(newContentObj); + ContentAPI.updateContentMetadata(req.ctx, req.params.contentId, req.body, (err, newContentObj) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(200).send(newContentObj); + }); }); /** @@ -390,18 +437,13 @@ OAE.tenantRouter.on('get', '/api/content/:contentId/download', (req, res) => { * @HttpResponse 404 Content not available */ OAE.tenantRouter.on('get', '/api/content/:contentId/download/:revisionId', (req, res) => { - ContentAPI.getRevisionDownloadInfo( - req.ctx, - req.params.contentId, - req.params.revisionId, - (err, downloadInfo) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return _handleDownload(res, downloadInfo, true); + ContentAPI.getRevisionDownloadInfo(req.ctx, req.params.contentId, req.params.revisionId, (err, downloadInfo) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + return _handleDownload(res, downloadInfo, true); + }); }); /** @@ -427,34 +469,30 @@ OAE.tenantRouter.on('post', '/api/content/:contentId/newversion', (req, res) => return res.status(400).send('Missing file parameter'); } - ContentAPI.updateFileBody( - req.ctx, - req.params.contentId, - req.files.file, - (err, updatedContentObj) => { - if (err) { - return res.status(err.code).send(err.msg); - } - // Set the response type to text/plain, as the UI uses an iFrame upload mechanism to support IE9 - // file uploads. If the response type is not set to text/plain, IE9 will try to download the response. - res.set('Content-Type', 'text/plain'); - res.status(200).send(updatedContentObj); + ContentAPI.updateFileBody(req.ctx, req.params.contentId, req.files.file, (err, updatedContentObj) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + // Set the response type to text/plain, as the UI uses an iFrame upload mechanism to support IE9 + // file uploads. If the response type is not set to text/plain, IE9 will try to download the response. + res.set('Content-Type', 'text/plain'); + res.status(200).send(updatedContentObj); + }); }); /** * @REST postContentContentIdJoin * - * Join a collaborative document + * Join a collaborative document or spreadsheet * * @Server tenant * @Method POST * @Path /content/{contentId}/join * @PathParam {string} contentId The id of the collaborative document to join * @Return {CollabdocJoinInfo} Information on how to join the collaborative document - * @HttpResponse 200 Joined collabdoc - * @HttpResponse 400 This is not a collaborative document + * @HttpResponse 200 Joined collabdoc or spreadsheet + * @HttpResponse 400 This is not a collaborative document or spreadsheet * @HttpResponse 401 You need to be a manager of this piece of content to be able to join it * @HttpResponse 404 Content not available */ @@ -463,6 +501,7 @@ OAE.tenantRouter.on('post', '/api/content/:contentId/join', (req, res) => { if (err) { return res.status(err.code).send(err.msg); } + res.status(200).send(data); }); }); @@ -490,62 +529,57 @@ OAE.tenantRouter.on('post', '/api/content/:contentId/join', (req, res) => { * @HttpResponse 401 Only administrators can attach preview items to a content item * @HttpResponse 404 Content not available */ -OAE.tenantRouter.on( - 'post', - '/api/content/:contentId/revisions/:revisionId/previews', - (req, res) => { - let contentMetadata = null; - let previewMetadata = null; - let sizes = null; - let files = null; - try { - contentMetadata = JSON.parse(req.body.contentMetadata); - previewMetadata = JSON.parse(req.body.previewMetadata); - sizes = JSON.parse(req.body.sizes); - - if (req.body.links) { - files = JSON.parse(req.body.links); - } - } catch (error) { - let invalidField = null; - if (!contentMetadata) { - invalidField = 'contentMetadata'; - } else if (!previewMetadata) { - invalidField = 'previewMetadata'; - } else if (!sizes) { - invalidField = 'sizes'; - } else if (!files) { - invalidField = 'links'; - } +OAE.tenantRouter.on('post', '/api/content/:contentId/revisions/:revisionId/previews', (req, res) => { + let contentMetadata = null; + let previewMetadata = null; + let sizes = null; + let files = null; + try { + contentMetadata = JSON.parse(req.body.contentMetadata); + previewMetadata = JSON.parse(req.body.previewMetadata); + sizes = JSON.parse(req.body.sizes); - return res - .status(400) - .send('Malformed metadata object. Expected proper JSON for: ' + invalidField); + if (req.body.links) { + files = JSON.parse(req.body.links); } - - if (req.files) { - files = files || {}; - _.extend(files, req.files); + } catch (error) { + let invalidField = null; + if (!contentMetadata) { + invalidField = 'contentMetadata'; + } else if (!previewMetadata) { + invalidField = 'previewMetadata'; + } else if (!sizes) { + invalidField = 'sizes'; + } else if (!files) { + invalidField = 'links'; } - ContentAPI.setPreviewItems( - req.ctx, - req.params.contentId, - req.params.revisionId, - req.body.status, - files, - sizes, - contentMetadata, - previewMetadata, - err => { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(201).end(); - } - ); + return res.status(400).send('Malformed metadata object. Expected proper JSON for: ' + invalidField); + } + + if (req.files) { + files = files || {}; + _.extend(files, req.files); } -); + + ContentAPI.setPreviewItems( + req.ctx, + req.params.contentId, + req.params.revisionId, + req.body.status, + files, + sizes, + contentMetadata, + previewMetadata, + err => { + if (err) { + return res.status(err.code).send(err.msg); + } + + res.status(201).end(); + } + ); +}); /** * @REST getContentContentIdRevisionsRevisionIdPreviews @@ -564,18 +598,13 @@ OAE.tenantRouter.on( * @HttpResponse 404 Content not available */ OAE.tenantRouter.on('get', '/api/content/:contentId/revisions/:revisionId/previews', (req, res) => { - ContentAPI.getPreviewItems( - req.ctx, - req.params.contentId, - req.params.revisionId, - (err, previews) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - res.status(200).send(previews); + ContentAPI.getPreviewItems(req.ctx, req.params.contentId, req.params.revisionId, (err, previews) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(200).send(previews); + }); }); /** @@ -603,31 +632,27 @@ OAE.tenantRouter.on('get', '/api/content/:contentId/revisions/:revisionId/previe * @HttpResponse 401 Invalid content signature data for accessing previews * @HttpResponse 404 Preview not available */ -OAE.tenantRouter.on( - 'get', - '/api/content/:contentId/revisions/:revisionId/previews/:item', - (req, res) => { - const signature = { - signature: req.query.signature, - expires: req.query.expires, - lastModified: req.query.lastmodified - }; - ContentAPI.getSignedPreviewDownloadInfo( - req.ctx, - req.params.contentId, - req.params.revisionId, - req.params.item, - signature, - (err, downloadInfo) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return _handleDownload(res, downloadInfo, true); +OAE.tenantRouter.on('get', '/api/content/:contentId/revisions/:revisionId/previews/:item', (req, res) => { + const signature = { + signature: req.query.signature, + expires: req.query.expires, + lastModified: req.query.lastmodified + }; + ContentAPI.getSignedPreviewDownloadInfo( + req.ctx, + req.params.contentId, + req.params.revisionId, + req.params.item, + signature, + (err, downloadInfo) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); - } -); + + return _handleDownload(res, downloadInfo, true); + } + ); +}); /** * @REST getContentContentIdRevisions @@ -648,18 +673,13 @@ OAE.tenantRouter.on( */ OAE.tenantRouter.on('get', '/api/content/:contentId/revisions', (req, res) => { const limit = OaeUtil.getNumberParam(req.query.limit, 10, 1, 25); - ContentAPI.getRevisions( - req.ctx, - req.params.contentId, - req.query.start, - limit, - (err, revisions, nextToken) => { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(200).send({ results: revisions, nextToken }); + ContentAPI.getRevisions(req.ctx, req.params.contentId, req.query.start, limit, (err, revisions, nextToken) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(200).send({ results: revisions, nextToken }); + }); }); /** @@ -683,6 +703,7 @@ OAE.tenantRouter.on('get', '/api/content/:contentId/revisions/:revisionId', (req if (err) { return res.status(err.code).send(err.msg); } + res.status(200).send(revision); }); }); @@ -706,17 +727,13 @@ OAE.tenantRouter.on('get', '/api/content/:contentId/revisions/:revisionId', (req * @HttpResponse 404 Content not available */ OAE.tenantRouter.on('post', '/api/content/:contentId/revisions/:revisionId/restore', (req, res) => { - ContentAPI.restoreRevision( - req.ctx, - req.params.contentId, - req.params.revisionId, - (err, newRevision) => { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(200).send(newRevision); + ContentAPI.restoreRevision(req.ctx, req.params.contentId, req.params.revisionId, (err, newRevision) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(200).send(newRevision); + }); }); /** @@ -748,6 +765,7 @@ OAE.tenantRouter.on('get', '/api/content/:contentId/members', (req, res) => { if (err) { return res.status(err.code).send(err.msg); } + res.status(200).send({ results: members, nextToken }); } ); @@ -780,10 +798,12 @@ OAE.tenantRouter.on('post', '/api/content/:contentId/members', (req, res) => { for (let r = 0; r < requestKeys.length; r++) { req.body[requestKeys[r]] = OaeUtil.castToBoolean(req.body[requestKeys[r]]); } + ContentAPI.setContentPermissions(req.ctx, req.params.contentId, req.body, err => { if (err) { return res.status(err.code).send(err.msg); } + res.status(200).end(); }); }); @@ -871,6 +891,7 @@ OAE.tenantRouter.on('post', '/api/content/:contentId/share', (req, res) => { if (err) { return res.status(err.code).send(err.msg); } + res.status(200).end(); }); }); @@ -905,18 +926,13 @@ OAE.tenantRouter.on('post', '/api/content/:contentId/share', (req, res) => { * @HttpResponse 500 Failed to create a new message */ OAE.tenantRouter.on('post', '/api/content/:contentId/messages', (req, res) => { - ContentAPI.createComment( - req.ctx, - req.params.contentId, - req.body.body, - req.body.replyTo, - (err, message) => { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(201).send(message); + ContentAPI.createComment(req.ctx, req.params.contentId, req.body.body, req.body.replyTo, (err, message) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(201).send(message); + }); }); /** @@ -941,18 +957,13 @@ OAE.tenantRouter.on('post', '/api/content/:contentId/messages', (req, res) => { */ OAE.tenantRouter.on('get', '/api/content/:contentId/messages', (req, res) => { const limit = OaeUtil.getNumberParam(req.query.limit, 10, 1, 25); - ContentAPI.getComments( - req.ctx, - req.params.contentId, - req.query.start, - limit, - (err, messages, nextToken) => { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(200).send({ results: messages, nextToken }); + ContentAPI.getComments(req.ctx, req.params.contentId, req.query.start, limit, (err, messages, nextToken) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(200).send({ results: messages, nextToken }); + }); }); /** @@ -988,6 +999,7 @@ OAE.tenantRouter.on('delete', '/api/content/:contentId/messages/:created', (req, if (err) { return res.status(err.code).send(err.msg); } + res.status(200).send(deleted); }); }); @@ -1020,6 +1032,7 @@ OAE.tenantRouter.on('get', '/api/content/library/:principalId', (req, res) => { if (err) { return res.status(err.code).send(err.msg); } + res.status(200).send({ results: items, nextToken }); } ); @@ -1049,17 +1062,13 @@ OAE.tenantRouter.on('get', '/api/content/library/:principalId', (req, res) => { * @HttpResponse 401 You must be authenticated to remove a piece of content from a library */ OAE.tenantRouter.on('delete', '/api/content/library/:principalId/:contentId', (req, res) => { - ContentAPI.removeContentFromLibrary( - req.ctx, - req.params.principalId, - req.params.contentId, - err => { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(200).end(); + ContentAPI.removeContentFromLibrary(req.ctx, req.params.principalId, req.params.contentId, err => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(200).end(); + }); }); /** @@ -1115,10 +1124,7 @@ const _handleDownload = function(res, downloadInfo, expiresMax) { res.setHeader('Cache-Control', 'max-age=315360000'); } - res.setHeader( - 'Content-Disposition', - 'attachment; filename="' + querystring.escape(downloadInfo.filename) + '"' - ); + res.setHeader('Content-Disposition', 'attachment; filename="' + querystring.escape(downloadInfo.filename) + '"'); res.status(204).send(downloadStrategy.target); // A redirect strategy will invoke a redirect to the target diff --git a/packages/oae-content/lib/restmodel.js b/packages/oae-content/lib/restmodel.js index 92ad944267..eba6e99d41 100644 --- a/packages/oae-content/lib/restmodel.js +++ b/packages/oae-content/lib/restmodel.js @@ -43,6 +43,26 @@ * @Property {string} visibility The visibility of the collaborative document [loggedin,private,public] */ +/** + * @RESTModel BasicContentCollabsheet + * + * @Required [created,createdBy,displayName,ethercalcRoomId,id,lastModified,latestRevisionId,profilePath,resourceSubType,resourceType,tenant,visibility] + * @Property {number} created The timestamp (millis since epoch) at which the collaborative spreadsheet was created + * @Property {string} createdBy The id of the user who created the collaborative spreadsheet + * @Property {string} description A longer description for the collaborative spreadsheet + * @Property {string} displayName The display name of the collaborative spreadsheet + * @Property {string} ethercalcRoomId The id of the collaborative spreadsheet's corresponding Ethercalc room + * @Property {string} id The id of the collaborative spreadsheet + * @Property {number} lastModified The timestamp (millis since epoch) at which the collaborative spreadsheet was last modified + * @Property {string} latestRevisionId The id of the current collaborative spreadsheet revision + * @Property {Previews} previews The thumbnails for the collaborative spreadsheet + * @Property {string} profilePath The relative path to the collaborative spreadsheet + * @Property {string} resourceSubType The content item type [collabsheet] + * @Property {string} resourceType The resource type of the content item [content] + * @Property {Tenant} tenant The tenant to which this collaborative spreadsheet is associated + * @Property {string} visibility The visibility of the collaborative spreadsheet [loggedin,private,public] + */ + /** * @RESTModel BasicContentFile * @@ -106,6 +126,20 @@ * @Property {string} thumbnailUrl The relative path to the revision thumbnail */ +/** + * @RESTModel CollabsheetRevision + * + * @Required [contentId,created,createdBy,revisionId] + * @Property {string} contentId The id of the collaborative spreadsheet associated to the revision + * @Property {number} created The timestamp (millis since epoch) at which the revision was created + * @Property {BasicUser} createdBy The user who created the revision + * @Property {string} ethercalcHtml The full HTML content of the collaborative spreadsheet + * @Property {string} ethercalcSnapshot The contents of the collaborative spreadsheet in socialcalc format + * @Property {Previews} previews The thumbnails for the revision + * @Property {string} revisionId The id of the revision + * @Property {string} thumbnailUrl The relative path to the revision thumbnail + */ + /** * @RESTModel Content * @@ -139,6 +173,29 @@ * @Property {string} visibility The visibility of the collaborative document [loggedin,private,public] */ +/** + * @RESTModel ContentCollabsheet + * + * @Required [canShare,created,createdBy,displayName,ethercalcRoomId,id,isManager,lastModified,latestRevisionId,profilePath,resourceSubType,resourceType,tenant,visibility] + * @Property {boolean} canShare Whether the current user is allowed to share the collaborative spreadsheet + * @Property {number} created The timestamp (millis since epoch) at which the collaborative spreadsheet was created + * @Property {BasicUser} createdBy The user who created the collaborative spreadsheet + * @Property {string} description A longer description for the collaborative spreadsheet + * @Property {string} displayName The display name of the collaborative spreadsheet + * @Property {string} ethercalcRoomId The id of the collaborative spreadsheet's corresponding Ethercalc room + * @Property {string} id The id of the collaborative spreadsheet + * @Property {boolean} isManager Whether the current user is a manager of the collaborative spreadsheet + * @Property {number} lastModified The timestamp (millis since epoch) at which the collaborative spreadsheet was last modified + * @Property {CollabsheetRevision} latestRevision The current collaborative spreadsheet revision + * @Property {string} latestRevisionId The id of the current collaborative spreadsheet revision + * @Property {Previews} previews The thumbnails for the collaborative spreadsheet + * @Property {string} profilePath The relative path to the collaborative spreadsheet + * @Property {string} resourceSubType The content item type [collabsheet] + * @Property {string} resourceType The resource type of the content item [content] + * @Property {BasicTenant} tenant The tenant to which this collaborative spreadsheet is associated + * @Property {string} visibility The visibility of the collaborative spreadsheet [loggedin,private,public] + */ + /** * @RESTModel ContentFile * @@ -294,6 +351,7 @@ * * @Required [] * @Property {CollabdocRevision} (collabdocRevision) Used when the revision is a collaborative document revision + * @Property {CollabsheetRevision} (collabsheetRevision) Used when the revision is a collaborative spreadsheet revision * @Property {FileRevision} (fileRevision) Used when the revision is a file revision * @Property {LinkRevision} (linkRevision) Used when the revision is a link revision */ diff --git a/packages/oae-content/lib/search.js b/packages/oae-content/lib/search.js index 7c41e51010..f47ff0ff45 100644 --- a/packages/oae-content/lib/search.js +++ b/packages/oae-content/lib/search.js @@ -29,6 +29,9 @@ const { ContentConstants } = require('oae-content/lib/constants'); const ContentDAO = require('oae-content/lib/internal/dao'); const ContentUtil = require('oae-content/lib/internal/util'); +const COLLABDOC = 'collabdoc'; +const COLLABSHEET = 'collabsheet'; + /** * Initializes the child search documents for the Content module * @@ -51,6 +54,7 @@ module.exports.init = function(callback) { if (err) { return callback(err); } + return MessageBoxSearch.registerMessageSearchDocument( ContentConstants.search.MAPPING_CONTENT_COMMENT, ['content'], @@ -265,38 +269,34 @@ const _produceContentBodyDocuments = function(resources, callback, _documents, _ } const { tenantAlias } = AuthzUtil.getResourceFromId(revision.previewsId); - ContentUtil.getStorageBackend(null, preview.uri).get( - tenantAlias, - preview.uri, - (err, file) => { - if (err) { - _errs = _.union(_errs, [err]); - return _produceContentBodyDocuments(resources, callback, _documents, _errs); + ContentUtil.getStorageBackend(null, preview.uri).get(tenantAlias, preview.uri, (err, file) => { + if (err) { + _errs = _.union(_errs, [err]); + return _produceContentBodyDocuments(resources, callback, _documents, _errs); + } + + fs.readFile(file.path, (err, data) => { + if (!err) { + const childDoc = SearchUtil.createChildSearchDocument( + ContentConstants.search.MAPPING_CONTENT_BODY, + resource.id, + // eslint-disable-next-line camelcase + { content_body: data.toString('utf8') } + ); + _documents.push(childDoc); } - fs.readFile(file.path, (err, data) => { - if (!err) { - const childDoc = SearchUtil.createChildSearchDocument( - ContentConstants.search.MAPPING_CONTENT_BODY, - resource.id, - // eslint-disable-next-line camelcase - { content_body: data.toString('utf8') } - ); - _documents.push(childDoc); + // In all cases, the file should be removed again + fs.unlink(file.path, err => { + if (err) { + _errs = _.union(_errs, [err]); } - // In all cases, the file should be removed again - fs.unlink(file.path, err => { - if (err) { - _errs = _.union(_errs, [err]); - } - - // Move on to the next file - _produceContentBodyDocuments(resources, callback, _documents, _errs); - }); + // Move on to the next file + _produceContentBodyDocuments(resources, callback, _documents, _errs); }); - } - ); + }); + }); }); }); }; @@ -319,6 +319,7 @@ const _produceContentSearchDocuments = function(resources, callback) { // If the content items could not be found, there isn't much we can do } + if (_.isEmpty(contentItems)) { return callback(null, docs); } @@ -329,9 +330,7 @@ const _produceContentSearchDocuments = function(resources, callback) { } _.each(contentItems, contentItem => { - docs.push( - _produceContentSearchDocument(contentItem, revisionsById[contentItem.latestRevisionId]) - ); + docs.push(_produceContentSearchDocument(contentItem, revisionsById[contentItem.latestRevisionId])); }); return callback(null, docs); @@ -351,7 +350,7 @@ const _getRevisionItems = function(contentItems, callback) { // Check if we need to fetch revisions const revisionsToRetrieve = []; _.each(contentItems, content => { - if (content.resourceSubType === 'collabdoc') { + if (content.resourceSubType === COLLABDOC || content.resourceSubType === COLLABSHEET) { revisionsToRetrieve.push(content.latestRevisionId); } }); @@ -362,7 +361,7 @@ const _getRevisionItems = function(contentItems, callback) { ContentDAO.Revisions.getMultipleRevisions( revisionsToRetrieve, - { fields: ['revisionId', 'etherpadHtml'] }, + { fields: ['revisionId', 'etherpadHtml', 'ethercalcHtml'] }, (err, revisions) => { if (err) { return callback(err); @@ -430,8 +429,10 @@ const _getContentItems = function(resources, callback) { const _produceContentSearchDocument = function(content, revision) { // Allow full-text search on name and description, but only if they are specified. We also sort on this text let fullText = _.compact([content.displayName, content.description]).join(' '); - if (content.resourceSubType === 'collabdoc' && revision && revision.etherpadHtml) { + if (content.resourceSubType === COLLABDOC && revision && revision.etherpadHtml) { fullText += ' ' + revision.etherpadHtml; + } else if (content.resourceSubType === COLLABSHEET && revision && revision.ethercalcHtml) { + fullText += ` ${revision.ethercalcHtml}`; } // Add all properties for the resource document metadata @@ -441,9 +442,9 @@ const _produceContentSearchDocument = function(content, revision) { tenantAlias: content.tenant.alias, displayName: content.displayName, visibility: content.visibility, - // eslint-disable-next-line camelcase + // eslint-disable-next-line camelcase q_high: content.displayName, - // eslint-disable-next-line camelcase + // eslint-disable-next-line camelcase q_low: fullText, sort: content.displayName, dateCreated: content.created, @@ -504,11 +505,7 @@ const _transformContentDocuments = function(ctx, docs, callback) { // Add the full tenant object and profile path _.extend(result, { tenant: TenantsAPI.getTenant(result.tenantAlias).compact(), - profilePath: util.format( - '/content/%s/%s', - result.tenantAlias, - AuthzUtil.getResourceFromId(result.id).resourceId - ) + profilePath: util.format('/content/%s/%s', result.tenantAlias, AuthzUtil.getResourceFromId(result.id).resourceId) }); // If applicable, sign the thumbnailUrl so the current user can access it @@ -532,12 +529,12 @@ SearchAPI.registerSearchDocumentTransformer('content', _transformContentDocument SearchAPI.registerReindexAllHandler('content', callback => { /*! - * Handles each iteration of the ContentDAO iterate all method, firing tasks for all content to - * be reindexed. - * - * @see ContentDAO.Content#iterateAll - * @api private - */ + * Handles each iteration of the ContentDAO iterate all method, firing tasks for all content to + * be reindexed. + * + * @see ContentDAO.Content#iterateAll + * @api private + */ const _onEach = function(contentRows, done) { // Batch up this iteration of task resources const contentResources = []; diff --git a/packages/oae-content/lib/test/util.js b/packages/oae-content/lib/test/util.js index 09d2110274..35ed9031b0 100644 --- a/packages/oae-content/lib/test/util.js +++ b/packages/oae-content/lib/test/util.js @@ -17,6 +17,7 @@ const assert = require('assert'); const util = require('util'); const _ = require('underscore'); const ShortId = require('shortid'); +const async = require('async'); const AuthzTestUtil = require('oae-authz/lib/test/util'); const MqTestUtil = require('oae-util/lib/test/mq-util'); @@ -826,6 +827,7 @@ const createCollabDoc = function(adminRestContext, nrOfUsers, nrOfJoinedUsers, c callCallback(); }); }; + for (let i = 0; i < nrOfJoinedUsers; i++) { joinCollabDoc(i); } @@ -855,6 +857,69 @@ const publishCollabDoc = function(contentId, userId, callback) { MqTestUtil.whenTasksEmpty(ContentConstants.queue.ETHERPAD_PUBLISH, callback); }; +/** + * Create a set of test users and a collaborative spreadsheet. + * The `nrOfJoinedUsers` specifies how many users should join the spreadsheet + * + * @param {RestContext} adminRestContext An administrator rest context that can be used to create the users + * @param {Number} nrOfUsers The number of users that should be created + * @param {Number} nrOfJoinedUsers The number of users that should be joined in the spreadsheet. These will be the first `nrOfJoinedUsers` of the users hash + * @param {Function} callback Standard callback function + * @param {Object} callback.err An error that occurred, if any + * @param {Content} callback.contentObj The created collaborative spreadsheet + * @param {Object} callback.users The created users + * @param {Object} callback.user1 An object containing the user profile and a rest context for the first user of the set of users that was created + * @param {Object} callback.user.. An object containing the user profile and a rest context for the next user of the set of users that was created + */ +const createCollabsheet = function(adminRestContext, nrOfUsers, nrOfJoinedUsers, callback) { + TestsUtil.generateTestUsers(adminRestContext, nrOfUsers, function(err, users) { + assert.ok(!err); + + const userIds = _.keys(users); + const userValues = _.values(users); + + // Create a collaborative document where all the users are managers + const name = TestsUtil.generateTestUserId('collabdoc'); + RestAPI.Content.createCollabsheet( + userValues[0].restContext, + name, + 'description', + 'public', + userIds, + [], + [], + [], + function(err, contentObj) { + assert.ok(!err); + + const restContexts = _.pluck(userValues, 'restContext'); + async.each( + restContexts, + (eachUserToJoin, exit) => { + RestAPI.Content.joinCollabDoc(eachUserToJoin, contentObj.id, function(err, data) { + if (err) { + exit(err); + } + + assert.ok(!err); + exit(null, data); + }); + }, + err => { + if (err) { + callback(err); + } + + const callbackArgs = _.union([contentObj, users], userValues); + + return callback.apply(callback, callbackArgs); + } + ); + } + ); + }); +}; + /** * Purge the members library. See @LibraryTestUtil.assertPurgeFreshLibrary for more details * @@ -885,5 +950,6 @@ module.exports = { assertGetContentLibrarySucceeds, generateTestLinks, publishCollabDoc, - createCollabDoc + createCollabDoc, + createCollabsheet }; diff --git a/packages/oae-content/tests/test-collabdoc.js b/packages/oae-content/tests/test-collabdoc.js index d3f0d9588b..a0eaffeec7 100644 --- a/packages/oae-content/tests/test-collabdoc.js +++ b/packages/oae-content/tests/test-collabdoc.js @@ -72,7 +72,7 @@ describe('Collaborative documents', () => { assert.ok(!err); const ctx = _.values(users)[0].restContext; - // Check that we can only join a collaborative document + // Check that we can't join a content item that's not collaborative RestAPI.Content.createLink( ctx, 'Test Content', @@ -106,7 +106,7 @@ describe('Collaborative documents', () => { RestAPI.Content.joinCollabDoc(ctx, 'invalid-id', err => { assert.strictEqual(err.code, 400); - // Sanity check + // Sanity check - make sure we can join a collabdoc RestAPI.Content.joinCollabDoc(ctx, contentObj.id, (err, data) => { assert.ok(!err); assert.ok(data); @@ -154,17 +154,12 @@ describe('Collaborative documents', () => { id, etherpadPadId: 'padId' }; - const etherpadUrl = Etherpad.getPadUrl( - contentObjC, - 'userId', - 'sesionId', - 'authorId', - 'language' - ); + const etherpadUrl = Etherpad.getPadUrl(contentObjC, 'userId', 'sesionId', 'authorId', 'language'); const path = url.parse(etherpadUrl).pathname; if (!counts[path]) { counts[path] = 0; } + counts[path]++; } @@ -175,14 +170,8 @@ describe('Collaborative documents', () => { // The URLs should be evenly spread (allow for a maximum 5% deviation) const devA = counts[urls[0]] / (total / 2); const devB = counts[urls[1]] / (total / 2); - assert.ok( - devA > 0.95, - 'Expected a maximum deviation of 5%, deviation was: ' + Math.round((1 - devA) * 100) + '%' - ); - assert.ok( - devB > 0.95, - 'Expected a maximum deviation of 5%, deviation was: ' + Math.round((1 - devB) * 100) + '%' - ); + assert.ok(devA > 0.95, 'Expected a maximum deviation of 5%, deviation was: ' + Math.round((1 - devA) * 100) + '%'); + assert.ok(devB > 0.95, 'Expected a maximum deviation of 5%, deviation was: ' + Math.round((1 - devB) * 100) + '%'); // Re-configure Etherpad with the defaults Etherpad.refreshConfiguration(testConfig); @@ -200,60 +189,50 @@ describe('Collaborative documents', () => { // Simon creates a collaborative document that's private const name = TestsUtil.generateTestUserId(); - RestAPI.Content.createCollabDoc( - simonCtx, - name, - 'description', - 'private', - [], - [], - [], - [], - (err, contentObj) => { + RestAPI.Content.createCollabDoc(simonCtx, name, 'description', 'private', [], [], [], [], (err, contentObj) => { + assert.ok(!err); + + RestAPI.Content.joinCollabDoc(simonCtx, contentObj.id, (err, data) => { assert.ok(!err); + assert.ok(data); - RestAPI.Content.joinCollabDoc(simonCtx, contentObj.id, (err, data) => { - assert.ok(!err); - assert.ok(data); + // Branden has no access yet, so joining should result in a 401 + RestAPI.Content.joinCollabDoc(brandenCtx, contentObj.id, (err, data) => { + assert.strictEqual(err.code, 401); + assert.ok(!data); - // Branden has no access yet, so joining should result in a 401 - RestAPI.Content.joinCollabDoc(brandenCtx, contentObj.id, (err, data) => { - assert.strictEqual(err.code, 401); - assert.ok(!data); + // Share it with branden, viewers still can't edit(=join) though + const members = {}; + members[_.keys(users)[1]] = 'viewer'; + RestAPI.Content.updateMembers(simonCtx, contentObj.id, members, err => { + assert.ok(!err); - // Share it with branden, viewers still can't edit(=join) though - const members = {}; - members[_.keys(users)[1]] = 'viewer'; - RestAPI.Content.updateMembers(simonCtx, contentObj.id, members, err => { - assert.ok(!err); + // Branden can see the document, but he cannot join in and start editing it + RestAPI.Content.joinCollabDoc(brandenCtx, contentObj.id, (err, data) => { + assert.strictEqual(err.code, 401); + assert.ok(!data); - // Branden can see the document, but he cannot join in and start editing it - RestAPI.Content.joinCollabDoc(brandenCtx, contentObj.id, (err, data) => { - assert.strictEqual(err.code, 401); - assert.ok(!data); + // Now that we make Branden a manager, he should be able to join + members[_.keys(users)[1]] = 'manager'; + RestAPI.Content.updateMembers(simonCtx, contentObj.id, members, err => { + assert.ok(!err); - // Now that we make Branden a manager, he should be able to join - members[_.keys(users)[1]] = 'manager'; - RestAPI.Content.updateMembers(simonCtx, contentObj.id, members, err => { + // Branden should now be able to access it + RestAPI.Content.joinCollabDoc(brandenCtx, contentObj.id, (err, data) => { assert.ok(!err); + assert.ok(data); - // Branden should now be able to access it - RestAPI.Content.joinCollabDoc(brandenCtx, contentObj.id, (err, data) => { + // Add Stuart as an editor, he should be able to join + members[_.keys(users)[2]] = 'editor'; + RestAPI.Content.updateMembers(simonCtx, contentObj.id, members, err => { assert.ok(!err); - assert.ok(data); - // Add Stuart as an editor, he should be able to join - members[_.keys(users)[2]] = 'editor'; - RestAPI.Content.updateMembers(simonCtx, contentObj.id, members, err => { + // Stuart should now be able to access it + RestAPI.Content.joinCollabDoc(stuartCtx, contentObj.id, (err, data) => { assert.ok(!err); + assert.ok(data); - // Stuart should now be able to access it - RestAPI.Content.joinCollabDoc(stuartCtx, contentObj.id, (err, data) => { - assert.ok(!err); - assert.ok(data); - - return callback(); - }); + return callback(); }); }); }); @@ -261,8 +240,8 @@ describe('Collaborative documents', () => { }); }); }); - } - ); + }); + }); }); }); @@ -329,6 +308,7 @@ describe('Collaborative documents', () => { }); }); }; + doEditAndPublish(); }; @@ -345,15 +325,10 @@ describe('Collaborative documents', () => { RestAPI.Content.getContent(context, contentId, (err, contentObj) => { assert.ok(!err); - RestAPI.Content.getRevision( - context, - contentId, - contentObj.latestRevisionId, - (err, revision) => { - assert.ok(!err); - callback(contentObj, revision); - } - ); + RestAPI.Content.getRevision(context, contentId, contentObj.latestRevisionId, (err, revision) => { + assert.ok(!err); + callback(contentObj, revision); + }); }); }; @@ -375,47 +350,31 @@ describe('Collaborative documents', () => { const text = 'Only two things are infinite, the universe and human stupidity, and I am not sure about the former.'; editAndPublish(simon, contentObj, [text], () => { - getContentWithLatestRevision( - simon.restContext, - contentObj.id, - (updatedContentObj, updatedRevision) => { - assert.ok(updatedContentObj.latestRevision.etherpadHtml); - assert.ok(updatedRevision.etherpadHtml); - assert.notStrictEqual( - updatedContentObj.latestRevision.etherpadHtml, - revision.etherpadHtml - ); - assert.notStrictEqual(updatedRevision.etherpadHtml, revision.etherpadHtml); + getContentWithLatestRevision(simon.restContext, contentObj.id, (updatedContentObj, updatedRevision) => { + assert.ok(updatedContentObj.latestRevision.etherpadHtml); + assert.ok(updatedRevision.etherpadHtml); + assert.notStrictEqual(updatedContentObj.latestRevision.etherpadHtml, revision.etherpadHtml); + assert.notStrictEqual(updatedRevision.etherpadHtml, revision.etherpadHtml); - // Remove linebreaks and check if the text is correct - ContentTestUtil.assertEtherpadContentEquals(updatedRevision.etherpadHtml, text); + // Remove linebreaks and check if the text is correct + ContentTestUtil.assertEtherpadContentEquals(updatedRevision.etherpadHtml, text); - // If we make any further updates in etherpad they shouldn't show up yet from our API - setEtherpadText( - simon.user.id, - contentObj, - 'There are no facts, only interpretations.', - err => { - assert.ok(!err); + // If we make any further updates in etherpad they shouldn't show up yet from our API + setEtherpadText(simon.user.id, contentObj, 'There are no facts, only interpretations.', err => { + assert.ok(!err); - getContentWithLatestRevision( - simon.restContext, - contentObj.id, - (latestContentObj, latestRevision) => { - assert.ok(latestContentObj.latestRevision.etherpadHtml); - assert.ok(latestRevision.etherpadHtml); - assert.strictEqual( - latestContentObj.latestRevision.etherpadHtml, - updatedContentObj.latestRevision.etherpadHtml - ); - assert.strictEqual(latestRevision.etherpadHtml, updatedRevision.etherpadHtml); - return callback(); - } - ); - } - ); - } - ); + getContentWithLatestRevision(simon.restContext, contentObj.id, (latestContentObj, latestRevision) => { + assert.ok(latestContentObj.latestRevision.etherpadHtml); + assert.ok(latestRevision.etherpadHtml); + assert.strictEqual( + latestContentObj.latestRevision.etherpadHtml, + updatedContentObj.latestRevision.etherpadHtml + ); + assert.strictEqual(latestRevision.etherpadHtml, updatedRevision.etherpadHtml); + return callback(); + }); + }); + }); }); }); }); @@ -466,57 +425,39 @@ describe('Collaborative documents', () => { "I don't have to play by these rules or do these things... I can actually have my own kind of version." ]; editAndPublish(simon, contentObj, texts, () => { - RestAPI.Content.getRevisions( - simon.restContext, - contentObj.id, - null, - null, - (err, revisions) => { - assert.ok(!err); + RestAPI.Content.getRevisions(simon.restContext, contentObj.id, null, null, (err, revisions) => { + assert.ok(!err); - // We published our document 3 times, this should result in 4 revisions. (1 create + 3 publications) - assert.strictEqual(revisions.results.length, 4); + // We published our document 3 times, this should result in 4 revisions. (1 create + 3 publications) + assert.strictEqual(revisions.results.length, 4); - // Restore the second revision. The html on the content item and - // in etherpad should be updated - RestAPI.Content.restoreRevision( - simon.restContext, - contentObj.id, - revisions.results[1].revisionId, - err => { - assert.ok(!err); + // Restore the second revision. The html on the content item and + // in etherpad should be updated + RestAPI.Content.restoreRevision(simon.restContext, contentObj.id, revisions.results[1].revisionId, err => { + assert.ok(!err); - getContentWithLatestRevision( - simon.restContext, - contentObj.id, - (updatedContent, updatedRevision) => { - // Make sure the revisions feed doesn't have etherpadHtml in it - assert.ok(!revisions.results[1].etherpadHtml); - // Fetch the individual revision so we can verify the etherpadHtml is correct - RestAPI.Content.getRevision( - simon.restContext, - revisions.results[1].contentId, - revisions.results[1].revisionId, - (err, fullRev) => { - assert.strictEqual( - updatedContent.latestRevision.etherpadHtml, - fullRev.etherpadHtml - ); - assert.strictEqual(updatedRevision.etherpadHtml, fullRev.etherpadHtml); - - getEtherpadText(contentObj, (err, data) => { - assert.ok(!err); - assert.strictEqual(data.text, texts[1] + '\n\n'); - return callback(); - }); - } - ); - } - ); - } - ); - } - ); + getContentWithLatestRevision(simon.restContext, contentObj.id, (updatedContent, updatedRevision) => { + // Make sure the revisions feed doesn't have etherpadHtml in it + assert.ok(!revisions.results[1].etherpadHtml); + // Fetch the individual revision so we can verify the etherpadHtml is correct + RestAPI.Content.getRevision( + simon.restContext, + revisions.results[1].contentId, + revisions.results[1].revisionId, + (err, fullRev) => { + assert.strictEqual(updatedContent.latestRevision.etherpadHtml, fullRev.etherpadHtml); + assert.strictEqual(updatedRevision.etherpadHtml, fullRev.etherpadHtml); + + getEtherpadText(contentObj, (err, data) => { + assert.ok(!err); + assert.strictEqual(data.text, texts[1] + '\n\n'); + return callback(); + }); + } + ); + }); + }); + }); }); }); }); @@ -531,60 +472,43 @@ describe('Collaborative documents', () => { const texts = ['Any sufficiently advanced technology is indistinguishable from magic.']; editAndPublish(simon, contentObj, texts, () => { - RestAPI.Content.getRevisions( - simon.restContext, - contentObj.id, - null, - null, - (err, revisions) => { - assert.ok(!err); - assert.strictEqual(revisions.results.length, 2); + RestAPI.Content.getRevisions(simon.restContext, contentObj.id, null, null, (err, revisions) => { + assert.ok(!err); + assert.strictEqual(revisions.results.length, 2); - // Branden is not a manager, so he cannot restore anything - RestAPI.Content.restoreRevision( - branden.restContext, - contentObj.id, - revisions.results[0].revisionId, - err => { - assert.strictEqual(err.code, 401); - - // Elevate Branden to an editor and verify that he still can't restore old versions - const permissions = {}; - permissions[branden.user.id] = 'editor'; - RestAPI.Content.updateMembers( - simon.restContext, + // Branden is not a manager, so he cannot restore anything + RestAPI.Content.restoreRevision( + branden.restContext, + contentObj.id, + revisions.results[0].revisionId, + err => { + assert.strictEqual(err.code, 401); + + // Elevate Branden to an editor and verify that he still can't restore old versions + const permissions = {}; + permissions[branden.user.id] = 'editor'; + RestAPI.Content.updateMembers(simon.restContext, contentObj.id, permissions, err => { + assert.ok(!err); + + RestAPI.Content.restoreRevision( + branden.restContext, contentObj.id, - permissions, + revisions.results[0].revisionId, err => { - assert.ok(!err); + assert.strictEqual(err.code, 401); - RestAPI.Content.restoreRevision( - branden.restContext, - contentObj.id, - revisions.results[0].revisionId, - err => { - assert.strictEqual(err.code, 401); - - // Sanity check - RestAPI.Content.getRevisions( - simon.restContext, - contentObj.id, - null, - null, - (err, revisions) => { - assert.ok(!err); - assert.strictEqual(revisions.results.length, 2); - return callback(); - } - ); - } - ); + // Sanity check + RestAPI.Content.getRevisions(simon.restContext, contentObj.id, null, null, (err, revisions) => { + assert.ok(!err); + assert.strictEqual(revisions.results.length, 2); + return callback(); + }); } ); - } - ); - } - ); + }); + } + ); + }); }); }); }); @@ -599,65 +523,35 @@ describe('Collaborative documents', () => { const simonCtx = _.values(users)[0].restContext; const name = TestsUtil.generateTestUserId('collabdoc'); - RestAPI.Content.createCollabDoc( - simonCtx, - name, - 'description', - 'public', - [], - [], - [], - [], - (err, contentObj) => { - assert.ok(!err); + RestAPI.Content.createCollabDoc(simonCtx, name, 'description', 'public', [], [], [], [], (err, contentObj) => { + assert.ok(!err); - // Try updating any of the etherpad properties - RestAPI.Content.updateContent( - simonCtx, - contentObj.id, - { etherpadGroupId: 'bleh' }, - err => { - assert.strictEqual(err.code, 400); - RestAPI.Content.updateContent( - simonCtx, - contentObj.id, - { etherpadPadId: 'bleh' }, - err => { - assert.strictEqual(err.code, 400); - // Update a regular property - RestAPI.Content.updateContent( - simonCtx, - contentObj.id, - { displayName: 'bleh' }, - (err, updatedContentObj) => { - assert.ok(!err); - assert.ok(!updatedContentObj.downloadPath); - - // Double-check the the content item didn't change - RestAPI.Content.getContent( - simonCtx, - contentObj.id, - (err, latestContentObj) => { - assert.ok(!err); - assert.strictEqual( - contentObj.etherpadGroupId, - latestContentObj.etherpadGroupId - ); - assert.strictEqual( - contentObj.etherpadPadId, - latestContentObj.etherpadPadId - ); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + // Try updating any of the etherpad properties + RestAPI.Content.updateContent(simonCtx, contentObj.id, { etherpadGroupId: 'bleh' }, err => { + assert.strictEqual(err.code, 400); + RestAPI.Content.updateContent(simonCtx, contentObj.id, { etherpadPadId: 'bleh' }, err => { + assert.strictEqual(err.code, 400); + // Update a regular property + RestAPI.Content.updateContent( + simonCtx, + contentObj.id, + { displayName: 'bleh' }, + (err, updatedContentObj) => { + assert.ok(!err); + assert.ok(!updatedContentObj.downloadPath); + + // Double-check the the content item didn't change + RestAPI.Content.getContent(simonCtx, contentObj.id, (err, latestContentObj) => { + assert.ok(!err); + assert.strictEqual(contentObj.etherpadGroupId, latestContentObj.etherpadGroupId); + assert.strictEqual(contentObj.etherpadPadId, latestContentObj.etherpadPadId); + return callback(); + }); + } + ); + }); + }); + }); }); }); @@ -685,49 +579,31 @@ describe('Collaborative documents', () => { // Publish the document with no changes made to it ContentTestUtil.publishCollabDoc(contentObj.id, simon.user.id, () => { // Ensure it only has 1 revision, the initial one - RestAPI.Content.getRevisions( - simon.restContext, - contentObj.id, - null, - null, - (err, revisions) => { - assert.ok(!err); - assert.strictEqual(revisions.results.length, 1); + RestAPI.Content.getRevisions(simon.restContext, contentObj.id, null, null, (err, revisions) => { + assert.ok(!err); + assert.strictEqual(revisions.results.length, 1); - // Generate a new revision with some new text in it - editAndPublish(simon, contentObj, ['Some text'], () => { - // Ensure we now have 2 revisions - RestAPI.Content.getRevisions( - simon.restContext, - contentObj.id, - null, - null, - (err, revisions) => { + // Generate a new revision with some new text in it + editAndPublish(simon, contentObj, ['Some text'], () => { + // Ensure we now have 2 revisions + RestAPI.Content.getRevisions(simon.restContext, contentObj.id, null, null, (err, revisions) => { + assert.ok(!err); + assert.strictEqual(revisions.results.length, 2); + + // Make the same edit and publish, ensuring that a new revision is not + // created + editAndPublish(simon, contentObj, ['Some text'], () => { + // Ensure we still only have 2 revisions (1 empty, one with "Some + // Text") + RestAPI.Content.getRevisions(simon.restContext, contentObj.id, null, null, (err, revisions) => { assert.ok(!err); assert.strictEqual(revisions.results.length, 2); - - // Make the same edit and publish, ensuring that a new revision is not - // created - editAndPublish(simon, contentObj, ['Some text'], () => { - // Ensure we still only have 2 revisions (1 empty, one with "Some - // Text") - RestAPI.Content.getRevisions( - simon.restContext, - contentObj.id, - null, - null, - (err, revisions) => { - assert.ok(!err); - assert.strictEqual(revisions.results.length, 2); - return callback(); - } - ); - }); - } - ); + return callback(); + }); + }); }); - } - ); + }); + }); }); }); }); @@ -743,45 +619,28 @@ describe('Collaborative documents', () => { // the pad is made empty when we restore the empty revision further down editAndPublish(simon, contentObj, ['Some text'], () => { // Sanity-check our 2 revisions exist - RestAPI.Content.getRevisions( - simon.restContext, - contentObj.id, - null, - null, - (err, revisions) => { + RestAPI.Content.getRevisions(simon.restContext, contentObj.id, null, null, (err, revisions) => { + assert.ok(!err); + assert.strictEqual(revisions.results.length, 2); + + // Try to restore the empty revision + RestAPI.Content.restoreRevision(simon.restContext, contentObj.id, revisions.results[1].revisionId, err => { assert.ok(!err); - assert.strictEqual(revisions.results.length, 2); - // Try to restore the empty revision - RestAPI.Content.restoreRevision( - simon.restContext, - contentObj.id, - revisions.results[1].revisionId, - err => { - assert.ok(!err); + // Assert that the pad is made empty + Etherpad.getHTML(contentObj.id, contentObj.etherpadPadId, (err, html) => { + assert.ok(!err); + assert.ok(Etherpad.isContentEmpty(html)); - // Assert that the pad is made empty - Etherpad.getHTML(contentObj.id, contentObj.etherpadPadId, (err, html) => { - assert.ok(!err); - assert.ok(Etherpad.isContentEmpty(html)); - - // Assert that this created a third revision - RestAPI.Content.getRevisions( - simon.restContext, - contentObj.id, - null, - null, - (err, revisions) => { - assert.ok(!err); - assert.strictEqual(revisions.results.length, 3); - return callback(); - } - ); - }); - } - ); - } - ); + // Assert that this created a third revision + RestAPI.Content.getRevisions(simon.restContext, contentObj.id, null, null, (err, revisions) => { + assert.ok(!err); + assert.strictEqual(revisions.results.length, 3); + return callback(); + }); + }); + }); + }); }); }); }); diff --git a/packages/oae-content/tests/test-collabsheet.js b/packages/oae-content/tests/test-collabsheet.js new file mode 100644 index 0000000000..13f5f638d4 --- /dev/null +++ b/packages/oae-content/tests/test-collabsheet.js @@ -0,0 +1,222 @@ +/* + * Copyright 2018 Apereo Foundation (AF) Licensed under the + * Educational Community License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://opensource.org/licenses/ECL-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +const assert = require('assert'); +const _ = require('underscore'); + +const RestAPI = require('oae-rest'); +const TestsUtil = require('oae-tests'); + +const ContentTestUtil = require('oae-content/lib/test/util'); +const Ethercalc = require('oae-content/lib/internal/ethercalc'); + +describe('Collaborative spreadsheets', function() { + // Rest context that can be used every time we need to make a request as an anonymous user + let anonymousRestContext = null; + // Rest context that can be used every time we need to make a request as a tenant admin + let camAdminRestContext = null; + + // Once the server has started up, get the ethercalc configuration and store it in this variable + let testConfig = null; + + /** + * Function that will fill up the anonymous and tenant admin REST context + */ + before(function(callback) { + // Fill up anonymous rest context + anonymousRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); + // Fill up tenant admin rest contexts + camAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.cam.host); + // Get the original test config + testConfig = Ethercalc.getConfig(); + return callback(); + }); + + /** + * Test that verifies the request parameters get validated when joining a collaborative spreadsheet + */ + it('verify basic parameter validation when joining a collaborative spreadsheet', function(callback) { + TestsUtil.generateTestUsers(camAdminRestContext, 1, function(err, users) { + assert.ok(!err); + const ctx = _.values(users)[0].restContext; + + // Check that we can't join a content item that's not collaborative + RestAPI.Content.createLink( + ctx, + 'Test link', + 'Test description', + 'public', + 'http://www.oaeproject.org/', + [], + [], + [], + function(err, link) { + assert.ok(!err); + + RestAPI.Content.joinCollabDoc(ctx, link.id, function(err) { + assert.equal(err.code, 400); + + RestAPI.Content.createCollabsheet(ctx, 'Test sheet', 'description', 'private', [], [], [], [], function( + err, + contentObj + ) { + assert.ok(!err); + + RestAPI.Content.joinCollabDoc(ctx, ' ', function(err) { + assert.equal(err.code, 400); + + RestAPI.Content.joinCollabDoc(ctx, 'invalid-id', function(err) { + assert.equal(err.code, 400); + + // Test collabsheets can be joined + RestAPI.Content.joinCollabDoc(ctx, contentObj.id, function(err, data) { + assert.ok(!err); + assert.ok(data); + callback(); + }); + }); + }); + }); + }); + } + ); + }); + }); + + /** + * Test that verifies that you can only join a collaborative spreadsheet if you have manager or editor permissions + */ + it('verify joining a room respects the content permissions', function(callback) { + TestsUtil.generateTestUsers(camAdminRestContext, 3, function(err, users) { + assert.ok(!err); + const simonCtx = _.values(users)[0].restContext; + const brandenCtx = _.values(users)[1].restContext; + const stuartCtx = _.values(users)[2].restContext; + + // Simon creates a collaborative spreadsheet that's private + const name = TestsUtil.generateTestUserId(); + RestAPI.Content.createCollabsheet(simonCtx, name, 'description', 'private', [], [], [], [], function( + err, + contentObj + ) { + assert.ok(!err); + + RestAPI.Content.joinCollabDoc(simonCtx, contentObj.id, function(err, data) { + assert.ok(!err); + assert.ok(data); + + // Branden has no access yet, so joining should result in a 401 + RestAPI.Content.joinCollabDoc(brandenCtx, contentObj.id, function(err, data) { + assert.equal(err.code, 401); + assert.ok(!data); + + // Share it with branden, viewers still can't edit(=join) though + const members = {}; + members[_.keys(users)[1]] = 'viewer'; + RestAPI.Content.updateMembers(simonCtx, contentObj.id, members, function(err) { + assert.ok(!err); + + // Branden can see the spreadsheet, but he cannot join in and start editing it + RestAPI.Content.joinCollabDoc(brandenCtx, contentObj.id, function(err, data) { + assert.equal(err.code, 401); + assert.ok(!data); + + // Now that we make Branden a manager, he should be able to join + members[_.keys(users)[1]] = 'manager'; + RestAPI.Content.updateMembers(simonCtx, contentObj.id, members, function(err) { + assert.ok(!err); + + // Branden should now be able to access it + RestAPI.Content.joinCollabDoc(brandenCtx, contentObj.id, function(err, data) { + assert.ok(!err); + assert.ok(data); + + // Add Stuart as an editor, he should be able to join + members[_.keys(users)[2]] = 'editor'; + RestAPI.Content.updateMembers(simonCtx, contentObj.id, members, function(err) { + assert.ok(!err); + + // Stuart should now be able to access it + RestAPI.Content.joinCollabDoc(stuartCtx, contentObj.id, function(err, data) { + assert.ok(!err); + assert.ok(data); + + return callback(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + + /** + * Test that verifies that `ethercalcRoomId` cannot be set. + */ + it('verify that ethercalc related properties cannot be set on the content object', function(callback) { + TestsUtil.generateTestUsers(camAdminRestContext, 1, function(err, users) { + assert.ok(!err); + const simonCtx = _.values(users)[0].restContext; + + const name = TestsUtil.generateTestUserId('collabsheet'); + RestAPI.Content.createCollabsheet(simonCtx, name, 'description', 'public', [], [], [], [], function( + err, + contentObj + ) { + assert.ok(!err); + + // Try updating any of the ethercalc properties + RestAPI.Content.updateContent(simonCtx, contentObj.id, { ethercalcRoomId: 'bleh' }, function(err) { + assert.equal(err.code, 400); + // Update a regular property + RestAPI.Content.updateContent(simonCtx, contentObj.id, { displayName: 'bleh' }, function( + err, + updatedContentObj + ) { + assert.ok(!err); + assert.ok(!updatedContentObj.downloadPath); + + // Double-check the the content item didn't change + RestAPI.Content.getContent(simonCtx, contentObj.id, function(err, latestContentObj) { + assert.ok(!err); + assert.equal(contentObj.ethercalcGroupId, latestContentObj.ethercalcGroupId); + assert.equal(contentObj.ethercalcRoomId, latestContentObj.ethercalcRoomId); + return callback(); + }); + }); + }); + }); + }); + }); + + /** + * Test that verifies that a collabsheet is created and initialized with no content + */ + it('verify ethercalc spreadsheet starts with empty spreadsheet', function(callback) { + // Create a collaborative spreadsheet to test with + ContentTestUtil.createCollabsheet(camAdminRestContext, 1, 1, function(content, users, simon) { + // Ensure the content of the ethercalc starts as empty + Ethercalc.getHTML(content.ethercalcRoomId, function(err, html) { + assert.ok(!err); + assert.ok(Ethercalc.isContentEmpty(html)); + return callback(); + }); + }); + }); +}); diff --git a/packages/oae-content/tests/test-content.js b/packages/oae-content/tests/test-content.js index aa45ffb42d..3dfe0c20fc 100644 --- a/packages/oae-content/tests/test-content.js +++ b/packages/oae-content/tests/test-content.js @@ -24,8 +24,6 @@ const _ = require('underscore'); const AuthzAPI = require('oae-authz'); const AuthzTestUtil = require('oae-authz/lib/test/util'); const AuthzUtil = require('oae-authz/lib/util'); -const Config = require('oae-config').config('oae-content'); -const ConfigTestUtil = require('oae-config/lib/test/util'); const { Context } = require('oae-context'); const PreviewConstants = require('oae-preview-processor/lib/constants'); const PrincipalsTestUtil = require('oae-principals/lib/test/util'); @@ -40,6 +38,10 @@ const ContentAPI = require('oae-content'); const ContentTestUtil = require('oae-content/lib/test/util'); const ContentUtil = require('oae-content/lib/internal/util'); +const PUBLIC = 'public'; +const PRIVATE = 'private'; +const LOGGEDIN = 'loggedin'; + describe('Content', () => { // Rest context that can be used every time we need to make a request as an anonymous user let anonymousRestContext = null; @@ -72,10 +74,10 @@ describe('Content', () => { assert.ok(!err); /*! - * Task handler that will just drain the queue. - * - * @see MQ#bind - */ + * Task handler that will just drain the queue. + * + * @see MQ#bind + */ const _handleTaskDrain = function(data, mqCallback) { // Simply callback, which acknowledges the message without doing anything. mqCallback(); @@ -120,6 +122,7 @@ describe('Content', () => { if (err) { assert.fail('Could not create test user'); } + contexts[identifier] = { user: createdUser, restContext: TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host, userId, 'password') @@ -131,13 +134,13 @@ describe('Content', () => { ); }; - createUser('nicolaas', 'public', 'Nicolaas Matthijs'); - createUser('simon', 'loggedin', 'Simon Gaeremynck'); - createUser('bert', 'private', 'Bert Pareyn'); - createUser('branden', 'private', 'Branden Visser'); - createUser('anthony', 'public', 'Anthony Whyte'); - createUser('stuart', 'public', 'Stuart Freeman'); - createUser('ian', 'public', 'Ian Dolphin'); + createUser('nicolaas', PUBLIC, 'Nicolaas Matthijs'); + createUser('simon', LOGGEDIN, 'Simon Gaeremynck'); + createUser('bert', PRIVATE, 'Bert Pareyn'); + createUser('branden', PRIVATE, 'Branden Visser'); + createUser('anthony', PUBLIC, 'Anthony Whyte'); + createUser('stuart', PUBLIC, 'Stuart Freeman'); + createUser('ian', PUBLIC, 'Ian Dolphin'); }; /** @@ -156,7 +159,7 @@ describe('Content', () => { contexts.bert.restContext, 'UI Dev Team', 'UI Dev Group', - 'public', + PUBLIC, 'yes', [], [contexts.nicolaas.user.id], @@ -171,7 +174,7 @@ describe('Content', () => { contexts.branden.restContext, 'Back-end Dev Team', 'Back-end Dev Group', - 'public', + PUBLIC, 'yes', [], [contexts.simon.user.id], @@ -185,7 +188,7 @@ describe('Content', () => { contexts.anthony.restContext, 'OAE Team', 'OAE Team Group', - 'public', + PUBLIC, 'yes', [], [groups['ui-team'].id, groups['backend-team'].id, contexts.stuart.user.id], @@ -252,6 +255,7 @@ describe('Content', () => { assert.ok(err); assert.ok(!retrievedContent); } + // Check if the item comes back in the library RestAPI.Content.getLibrary(restCtx, libraryToCheck, null, 10, (err, contentItems) => { // If no logged in user is provided, we expect an error @@ -266,6 +270,7 @@ describe('Content', () => { } else { assert.ok(err); } + callback(); }); }); @@ -303,7 +308,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -365,7 +370,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 1', 'Test content description 1', - 'private', + PRIVATE, getFileStream, [], [], @@ -443,7 +448,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 1', 'Test content description 1', - 'private', + PRIVATE, getFileStream, [], [], @@ -512,7 +517,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 1', 'Test content description 1', - 'private', + PRIVATE, getFileStream, [], [], @@ -541,8 +546,8 @@ describe('Content', () => { }); /* - * Test that will verify the validation of signed urls. - */ + * Test that will verify the validation of signed urls. + */ it('verify validation of signed urls', callback => { setUpUsers(contexts => { // Generate a signed download url for Branden @@ -651,6 +656,7 @@ describe('Content', () => { Date.now = function() { return now + 15 * 24 * 60 * 60 * 1000; }; + RestUtil.performRestRequest( contexts.branden.restContext, '/api/download/signed', @@ -749,7 +755,7 @@ describe('Content', () => { contexts.bert.restContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -831,7 +837,7 @@ describe('Content', () => { bert.restContext, 'displayName', 'description', - 'public', + PUBLIC, 'http://www.oaeproject.org', [], [nicolaas.user.id], @@ -940,7 +946,7 @@ describe('Content', () => { contexts.bert.restContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -1055,7 +1061,7 @@ describe('Content', () => { contexts.bert.restContext, 'Test Content', 'Test content description', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -1173,7 +1179,7 @@ describe('Content', () => { contexts.bert.restContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -1244,7 +1250,7 @@ describe('Content', () => { ctx, 'Test Content', 'Test content description', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -1291,7 +1297,7 @@ describe('Content', () => { contexts.bert.restContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -1464,7 +1470,7 @@ describe('Content', () => { contexts.simon.restContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -1477,7 +1483,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 2', 'Test content description 2', - 'private', + PRIVATE, 'http://www.oaeproject.org/', [], [contexts.simon.user.id], @@ -1649,7 +1655,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -1700,7 +1706,7 @@ describe('Content', () => { * @param {RestContext} linkContext The RestContext object of a user to create the content item with * @param {RestContext} commentContext The RestContext object of a user to comment on the content item with * @param {RestContext} deleteContext The RestContext object of a user to delete the comment with - * @param {String} visibility The visibility of the content item that will be created ('public', 'loggedin' or 'private') + * @param {String} visibility The visibility of the content item that will be created (PUBLIC, LOGGEDIN or PRIVATE) * @param {String[]} managers An array of user IDs that will be added as managers to the newly created content * @param {String[]} members An array of user IDs that will be added as members (viewers) to the newly created content * @param {Function} callback Standard callback function @@ -1788,6 +1794,7 @@ describe('Content', () => { assert.ok(err); assert.ok(!softDeleted); } + // Delete comment as anonymous on piece of content (--> fail) _canDelete(bert, bert, anonymousRestContext, visibility, [], [], true, (err, softDeleted) => { assert.ok(err); @@ -1806,7 +1813,7 @@ describe('Content', () => { */ it('verify delete comment permissions public', callback => { setUpUsers(contexts => { - testDeleteCommentPermissions(contexts, 'public', true, callback); + testDeleteCommentPermissions(contexts, PUBLIC, true, callback); }); }); @@ -1815,7 +1822,7 @@ describe('Content', () => { */ it('verify delete comment permissions loggedin', callback => { setUpUsers(contexts => { - testDeleteCommentPermissions(contexts, 'loggedin', true, callback); + testDeleteCommentPermissions(contexts, LOGGEDIN, true, callback); }); }); @@ -1824,7 +1831,7 @@ describe('Content', () => { */ it('verify delete comment permissions private', callback => { setUpUsers(contexts => { - testDeleteCommentPermissions(contexts, 'private', false, callback); + testDeleteCommentPermissions(contexts, PRIVATE, false, callback); }); }); @@ -1838,7 +1845,7 @@ describe('Content', () => { contexts.bert.restContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -2002,6 +2009,7 @@ describe('Content', () => { assert.ok(err); assert.ok(!comment); } + // Verify that the comment was placed as a logged in user RestAPI.Content.getComments( contexts.simon.restContext, @@ -2062,7 +2070,7 @@ describe('Content', () => { */ it('verify create comment permissions public', callback => { setUpUsers(contexts => { - testCommentPermissions(contexts, 'public', true, callback); + testCommentPermissions(contexts, PUBLIC, true, callback); }); }); @@ -2071,7 +2079,7 @@ describe('Content', () => { */ it('verify create comment permissions loggedin', callback => { setUpUsers(contexts => { - testCommentPermissions(contexts, 'loggedin', true, callback); + testCommentPermissions(contexts, LOGGEDIN, true, callback); }); }); @@ -2080,7 +2088,7 @@ describe('Content', () => { */ it('verify create comment permissions private', callback => { setUpUsers(contexts => { - testCommentPermissions(contexts, 'private', false, callback); + testCommentPermissions(contexts, PRIVATE, false, callback); }); }); }); @@ -2114,7 +2122,7 @@ describe('Content', () => { anonymousRestContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -2128,7 +2136,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 2', 'Test content description 2', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -2145,7 +2153,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 3', null, - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -2160,7 +2168,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 4', longDescription, - 'public', + PUBLIC, null, [], [], @@ -2174,7 +2182,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 4', 'Test content description 4', - 'public', + PUBLIC, null, [], [], @@ -2188,7 +2196,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 5', 'Test content description 5', - 'public', + PUBLIC, 'Just a string', [], [], @@ -2202,11 +2210,12 @@ describe('Content', () => { for (let i = 0; i < 2500; i++) { longUrl += 'a'; } + RestAPI.Content.createLink( contexts.nicolaas.restContext, 'Test Content 5', 'Test content description 5', - 'public', + PUBLIC, longUrl, [], [], @@ -2221,7 +2230,7 @@ describe('Content', () => { contexts.nicolaas.restContext, null, 'Test content description 6', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -2236,7 +2245,7 @@ describe('Content', () => { contexts.nicolaas.restContext, longDisplayName, 'Test content description 6', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -2267,7 +2276,7 @@ describe('Content', () => { contentObj.id, (err, contentObj) => { assert.ok(!err); - assert.strictEqual(contentObj.visibility, 'public'); + assert.strictEqual(contentObj.visibility, PUBLIC); assert.ok(!contentObj.downloadPath); // Verify that an empty description is allowed @@ -2289,7 +2298,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 8', 'Test content description 8', - 'public', + PUBLIC, 'www.oaeproject.org', [], [], @@ -2348,7 +2357,7 @@ describe('Content', () => { anonymousRestContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, getFileStream, [], [], @@ -2362,7 +2371,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 2', 'Test content description 2', - 'public', + PUBLIC, getFileStream, [], [], @@ -2381,7 +2390,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 3', null, - 'public', + PUBLIC, getFileStream, [], [], @@ -2396,7 +2405,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test content', longDescription, - 'public', + PUBLIC, getFileStream, [], [], @@ -2412,7 +2421,7 @@ describe('Content', () => { contexts.nicolaas.restContext, null, 'Test content description 4', - 'public', + PUBLIC, getFileStream, [], [], @@ -2427,7 +2436,7 @@ describe('Content', () => { contexts.nicolaas.restContext, longDisplayName, 'Test content description 4', - 'public', + PUBLIC, getFileStream, [], [], @@ -2443,7 +2452,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 4', 'Test content description 4', - 'public', + PUBLIC, null, [], [], @@ -2471,7 +2480,7 @@ describe('Content', () => { contentObj.id, (err, contentObj) => { assert.ok(!err); - assert.strictEqual(contentObj.visibility, 'public'); + assert.strictEqual(contentObj.visibility, PUBLIC); assert.strictEqual( contentObj.downloadPath, '/api/content/' + @@ -2485,7 +2494,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 5', '', - 'public', + PUBLIC, getFileStream, [], [], @@ -2527,7 +2536,7 @@ describe('Content', () => { anonymousRestContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, [], [], [], @@ -2541,7 +2550,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 2', 'Test content description 2', - 'public', + PUBLIC, [], [], [], @@ -2558,7 +2567,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 3', null, - 'public', + PUBLIC, [], [], [], @@ -2573,7 +2582,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test content', longDescription, - 'public', + PUBLIC, [], [], [], @@ -2589,7 +2598,7 @@ describe('Content', () => { contexts.nicolaas.restContext, null, 'Test content description 4', - 'public', + PUBLIC, [], [], [], @@ -2604,7 +2613,7 @@ describe('Content', () => { contexts.nicolaas.restContext, longDisplayName, 'descripton', - 'public', + PUBLIC, [], [], [], @@ -2634,7 +2643,7 @@ describe('Content', () => { contentObj.id, (err, contentObj) => { assert.ok(!err); - assert.strictEqual(contentObj.visibility, 'private'); + assert.strictEqual(contentObj.visibility, PRIVATE); assert.ok(!contentObj.downloadPath); // Verify that an empty description is accepted @@ -2642,7 +2651,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 5', '', - 'public', + PUBLIC, [], [], [], @@ -2683,7 +2692,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -2743,7 +2752,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 1', 'Test content description 1', - 'loggedin', + LOGGEDIN, 'http://www.oaeproject.org/', [], [], @@ -2803,7 +2812,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 1', 'Test content description 1', - 'private', + PRIVATE, 'http://www.oaeproject.org/', [], [], @@ -2864,7 +2873,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 1', 'Test content description 1', - 'private', + PRIVATE, 'http://www.oaeproject.org/', [contexts.simon.user.id], [contexts.stuart.user.id], @@ -2949,7 +2958,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 2', 'Test content description 2', - 'private', + PRIVATE, getFileStream, [contexts.simon.user.id], [contexts.stuart.user.id], @@ -3034,7 +3043,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 2', 'Test content description 2', - 'private', + PRIVATE, [contexts.simon.user.id], [], [contexts.stuart.user.id], @@ -3116,14 +3125,14 @@ describe('Content', () => { const nico = _.values(users)[0]; const bert = _.values(users)[1]; - RestAPI.User.updateUser(bert.restContext, bert.user.id, { visibility: 'private' }, err => { + RestAPI.User.updateUser(bert.restContext, bert.user.id, { visibility: PRIVATE }, err => { assert.ok(!err); RestAPI.Content.createLink( nico.restContext, 'Test Content', 'Test content description', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [bert.user.id], [], @@ -3151,7 +3160,7 @@ describe('Content', () => { bert.restContext, 'Group title', 'Group description', - 'private', + PRIVATE, undefined, [], [], @@ -3162,7 +3171,7 @@ describe('Content', () => { nico.restContext, 'Test Content', 'Test content description', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [groupObj.id], [], @@ -3232,7 +3241,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -3478,6 +3487,7 @@ describe('Content', () => { assert.ok(err); assert.ok(!contentObj); } + // Check that it isn't part of his library RestAPI.Content.getLibrary( contexts.bert.restContext, @@ -3511,6 +3521,7 @@ describe('Content', () => { assert.ok(err); assert.ok(!contentObj); } + // Check that it is visible in the manager's library RestAPI.Content.getLibrary( anonymousRestContext, @@ -3525,6 +3536,7 @@ describe('Content', () => { } else { assert.strictEqual(items.results.length, 0); } + callback(); } ); @@ -3555,7 +3567,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -3597,7 +3609,7 @@ describe('Content', () => { RestAPI.Content.updateContent( contexts.nicolaas.restContext, contentObj.id, - { visibility: 'loggedin' }, + { visibility: LOGGEDIN }, err => { assert.ok(!err); @@ -3607,7 +3619,7 @@ describe('Content', () => { RestAPI.Content.updateContent( contexts.nicolaas.restContext, contentObj.id, - { visibility: 'private' }, + { visibility: PRIVATE }, err => { assert.ok(!err); @@ -3617,7 +3629,7 @@ describe('Content', () => { RestAPI.Content.updateContent( contexts.simon.restContext, contentObj.id, - { visibility: 'public' }, + { visibility: PUBLIC }, err => { assert.ok(err); @@ -3656,7 +3668,7 @@ describe('Content', () => { nicolaas.restContext, 'display name', 'description', - 'public', + PUBLIC, 'http://www.oaeproject.org', [], [], @@ -3698,6 +3710,7 @@ describe('Content', () => { for (let i = 0; i < 2500; i++) { longUrl += 'a'; } + RestAPI.Content.updateContent( nicolaas.restContext, contentObj.id, @@ -3738,7 +3751,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 2', 'Test content description 2', - 'public', + PUBLIC, getFileStream, [], [], @@ -3758,7 +3771,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, [], [], [], @@ -3794,7 +3807,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 2', 'Test content description 2', - 'public', + PUBLIC, 'http://www.oaeproject.org', [], [], @@ -3810,7 +3823,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 2', 'Test content description 2', - 'public', + PUBLIC, getFileStream, [], [], @@ -3918,7 +3931,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 2', 'Test content description 2', - 'public', + PUBLIC, getFileStream, [], [], @@ -4044,7 +4057,7 @@ describe('Content', () => { contexts.simon.restContext, 'Test Content 1', 'Test content description 1', - 'private', + PRIVATE, getFileStream, [], [], @@ -4091,7 +4104,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Apereo Foundation', 'The Apereo Foundation', - 'private', + PRIVATE, 'http://www.apereo.org/', [], [], @@ -4137,7 +4150,7 @@ describe('Content', () => { contexts.simon.restContext, 'Test Content 1', 'Test content description 1', - 'private', + PRIVATE, getFileStream, [], [], @@ -4192,7 +4205,7 @@ describe('Content', () => { contexts.simon.restContext, 'Test Content', 'Test content description', - 'private', + PRIVATE, getFileStream, [], [], @@ -4214,6 +4227,7 @@ describe('Content', () => { if (createdRevisions.length === 30) { return callback(createdRevisions); } + createRevisions(callback); } ); @@ -4273,7 +4287,7 @@ describe('Content', () => { contexts.simon.restContext, 'Test Content 1', 'Test content description 1', - 'private', + PRIVATE, getFileStream, [], [], @@ -4387,7 +4401,7 @@ describe('Content', () => { true, false, true, - privacy !== 'private', + privacy !== PRIVATE, () => { // Share the content with another user RestAPI.Content.shareContent( @@ -4403,7 +4417,7 @@ describe('Content', () => { true, false, true, - privacy !== 'private', + privacy !== PRIVATE, () => { // Try to delete the content as an anonymous user RestAPI.Content.deleteContent(anonymousRestContext, contentObj.id, err => { @@ -4569,7 +4583,7 @@ describe('Content', () => { */ it('verify public delete', callback => { setUpUsers(contexts => { - prepareDelete(contexts, 'public', callback); + prepareDelete(contexts, PUBLIC, callback); }); }); @@ -4578,7 +4592,7 @@ describe('Content', () => { */ it('verify logged in delete', callback => { setUpUsers(contexts => { - prepareDelete(contexts, 'loggedin', callback); + prepareDelete(contexts, LOGGEDIN, callback); }); }); @@ -4587,7 +4601,7 @@ describe('Content', () => { */ it('verify private delete', callback => { setUpUsers(contexts => { - prepareDelete(contexts, 'private', callback); + prepareDelete(contexts, PRIVATE, callback); }); }); @@ -4600,7 +4614,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 2', 'Test content description 2', - 'public', + PUBLIC, getFileStream, [], [], @@ -4636,15 +4650,15 @@ describe('Content', () => { }); /** - * Verify collabdoc editors can't delete + * Verify collabdoc or collabsheet editors can't delete */ - it("verify collabdoc editors can't delete", callback => { + it("verify collabdoc or collabsheet editors can't delete", callback => { setUpUsers(contexts => { RestAPI.Content.createCollabDoc( contexts.nicolaas.restContext, 'Test CollabDoc', 'Doc description', - 'public', + PUBLIC, [], [contexts.stuart.user.id], [], @@ -4656,7 +4670,27 @@ describe('Content', () => { RestAPI.Content.deleteContent(contexts.nicolaas.restContext, contentObj.id, err => { assert.ok(!err); - return callback(); + RestAPI.Content.createCollabsheet( + contexts.nicolaas.restContext, + 'Test collabsheet', + 'Description', + PUBLIC, + [], + [contexts.stuart.user.id], + [], + [], + function(err, contentObj) { + assert.ok(!err); + RestAPI.Content.deleteContent(contexts.stuart.restContext, contentObj.id, function(err) { + assert.strictEqual(err.code, 401); + + RestAPI.Content.deleteContent(contexts.nicolaas.restContext, contentObj.id, function(err) { + assert.ok(!err); + return callback(); + }); + }); + } + ); }); }); } @@ -4723,7 +4757,7 @@ describe('Content', () => { */ it('verify public content permissions', callback => { setUpUsers(contexts => { - setUpContentPermissions(contexts, 'public', contentObj => { + setUpContentPermissions(contexts, PUBLIC, contentObj => { // Get the piece of content as a non-associated user checkPieceOfContent( contexts.branden.restContext, @@ -4756,7 +4790,7 @@ describe('Content', () => { */ it('verify logged in content permissions', callback => { setUpUsers(contexts => { - setUpContentPermissions(contexts, 'loggedin', contentObj => { + setUpContentPermissions(contexts, LOGGEDIN, contentObj => { // Get the piece of content as a non-associated user checkPieceOfContent( contexts.branden.restContext, @@ -4789,7 +4823,7 @@ describe('Content', () => { */ it('verify private content permissions', callback => { setUpUsers(contexts => { - setUpContentPermissions(contexts, 'private', contentObj => { + setUpContentPermissions(contexts, PRIVATE, contentObj => { // Get the piece of content as a non-associated user checkPieceOfContent( contexts.branden.restContext, @@ -4828,7 +4862,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -4893,7 +4927,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -4961,7 +4995,7 @@ describe('Content', () => { contexts.nicolaas.restContext, 'Test Content 1', 'Test content description 1', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -5047,14 +5081,14 @@ describe('Content', () => { const nico = _.values(users)[0]; const bert = _.values(users)[1]; - RestAPI.User.updateUser(bert.restContext, bert.user.id, { visibility: 'private' }, err => { + RestAPI.User.updateUser(bert.restContext, bert.user.id, { visibility: PRIVATE }, err => { assert.ok(!err); RestAPI.Content.createLink( nico.restContext, 'Test Content', 'Test content description', - 'public', + PUBLIC, 'http://www.google.com', [], [], @@ -5087,7 +5121,7 @@ describe('Content', () => { bert.restContext, 'Group title', 'Group description', - 'private', + PRIVATE, undefined, [], [], @@ -5098,7 +5132,7 @@ describe('Content', () => { nico.restContext, 'Test Content', 'Test content description', - 'public', + PUBLIC, 'http://www.google.com', [], [], @@ -5132,7 +5166,7 @@ describe('Content', () => { nico.restContext, 'Test Content', 'Test content description', - 'public', + PUBLIC, 'http://www.google.com', [], [bert.user.id], @@ -5140,7 +5174,7 @@ describe('Content', () => { (err, contentObj) => { assert.ok(!err); - RestAPI.User.updateUser(bert.restContext, bert.user.id, { visibility: 'private' }, err => { + RestAPI.User.updateUser(bert.restContext, bert.user.id, { visibility: PRIVATE }, err => { assert.ok(!err); // Changing the role of a private user (that was already a member) should work @@ -5191,7 +5225,7 @@ describe('Content', () => { simon.restContext, 'Test Content', 'Test content description', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], memberIds, @@ -5231,7 +5265,7 @@ describe('Content', () => { ctx, 'Test Content', 'Test content description', - 'public', + PUBLIC, 'http://www.oaeproject.org/', [], [], @@ -5255,15 +5289,15 @@ describe('Content', () => { }); /** - * Test that verifies collabdoc editors can't change permissions, but can share + * Test that verifies collabdoc or collabsheets editors can't change permissions, but can share */ - it("verify collabdoc editors can't change permissions", callback => { + it("verify collabdoc or collabsheets editors can't change permissions", callback => { setUpUsers(contexts => { RestAPI.Content.createCollabDoc( contexts.nicolaas.restContext, 'Test CollabDoc', 'Doc description', - 'public', + PUBLIC, [], [contexts.stuart.user.id], [], @@ -5285,7 +5319,40 @@ describe('Content', () => { contexts.stuart.restContext, contentObj.id, _.keys(members), - callback + function() { + // Make sure same is true for collaborative spreadsheets + RestAPI.Content.createCollabsheet( + contexts.stuart.restContext, + 'Test collabsheet', + 'Sheet description', + PUBLIC, + [], + [contexts.nicolaas.user.id], + [], + [], + function(err, contentObj) { + assert.ok(!err); + const members = {}; + members[contexts.anthony.user.id] = 'viewer'; + ContentTestUtil.assertUpdateContentMembersFails( + contexts.stuart.restContext, + contexts.nicolaas.restContext, + contentObj.id, + members, + 401, + function() { + ContentTestUtil.assertShareContentSucceeds( + contexts.stuart.restContext, + contexts.nicolaas.restContext, + contentObj.id, + _.keys(members), + callback + ); + } + ); + } + ); + } ); } ); @@ -5308,7 +5375,7 @@ describe('Content', () => { creatingUserInfo.restContext, randomString, randomString, - 'public', + PUBLIC, 'http://www.oaeproject.org', [], [], @@ -5349,7 +5416,7 @@ describe('Content', () => { creatingUserInfo.restContext, randomString, randomString, - 'public', + PUBLIC, 'http://www.oaeproject.org', [], [], @@ -5502,6 +5569,7 @@ describe('Content', () => { } else { assert.ok(err); } + checkPieceOfContent( shareWith.restContext, shareWith.user ? shareWith.user.id : null, @@ -5521,6 +5589,7 @@ describe('Content', () => { } else { assert.ok(err); } + callback(); }); } @@ -5536,7 +5605,7 @@ describe('Content', () => { it('verify public sharing', callback => { setUpUsers(contexts => { // Create a public content item - prepareSharing(contexts, 'public', contentObj => { + prepareSharing(contexts, PUBLIC, contentObj => { // Share as content owner const expectedMembers = {}; expectedMembers[contexts.nicolaas.user.id] = 'manager'; @@ -5623,7 +5692,7 @@ describe('Content', () => { it('verify logged in sharing', callback => { setUpUsers(contexts => { // Create a loggedin content item - prepareSharing(contexts, 'loggedin', contentObj => { + prepareSharing(contexts, LOGGEDIN, contentObj => { // Share as content owner const expectedMembers = {}; expectedMembers[contexts.nicolaas.user.id] = 'manager'; @@ -5710,7 +5779,7 @@ describe('Content', () => { it('verify private sharing', callback => { setUpUsers(contexts => { // Create a private content item - prepareSharing(contexts, 'private', contentObj => { + prepareSharing(contexts, PRIVATE, contentObj => { // Share as content owner const expectedMembers = {}; expectedMembers[contexts.nicolaas.user.id] = 'manager'; @@ -5783,7 +5852,7 @@ describe('Content', () => { it('verify multiple sharing', callback => { setUpUsers(contexts => { // Create a piece of content - prepareSharing(contexts, 'private', contentObj => { + prepareSharing(contexts, PRIVATE, contentObj => { // Share with multiple people at the same time let toShare = [contexts.simon.user.id, contexts.ian.user.id, contexts.stuart.user.id]; RestAPI.Content.shareContent(contexts.nicolaas.restContext, contentObj.id, toShare, err => { @@ -5874,7 +5943,7 @@ describe('Content', () => { actor.restContext, 'Test Content 1', 'Test content description 1', - 'private', + PRIVATE, 'http://www.oaeproject.org/', [], [], @@ -5958,7 +6027,7 @@ describe('Content', () => { true, false, true, - privacy !== 'private', + privacy !== PRIVATE, () => { checkPieceOfContent( contexts.nicolaas.restContext, @@ -5967,7 +6036,7 @@ describe('Content', () => { true, false, false, - privacy !== 'private', + privacy !== PRIVATE, () => { checkPieceOfContent( contexts.bert.restContext, @@ -5976,7 +6045,7 @@ describe('Content', () => { true, false, false, - privacy !== 'private', + privacy !== PRIVATE, () => { // Check that it shows in UI Dev Team's library RestAPI.Content.getLibrary( @@ -6038,20 +6107,20 @@ describe('Content', () => { contexts.stuart.restContext, contexts.stuart.user.id, contentObj, - privacy !== 'private', + privacy !== PRIVATE, false, false, - privacy !== 'private', + privacy !== PRIVATE, () => { // Check that Branden doesn't have access checkPieceOfContent( contexts.branden.restContext, contexts.branden.user.id, contentObj, - privacy !== 'private', + privacy !== PRIVATE, false, false, - privacy !== 'private', + privacy !== PRIVATE, () => { // Share with the OAE Team group RestAPI.Content.shareContent( @@ -6067,7 +6136,7 @@ describe('Content', () => { true, false, false, - privacy !== 'private', + privacy !== PRIVATE, () => { // Check that Branden has access checkPieceOfContent( @@ -6077,7 +6146,7 @@ describe('Content', () => { true, false, false, - privacy !== 'private', + privacy !== PRIVATE, () => { // Check that it shows in OAE Team and UI Dev team's library and not in the Back-End Team's library RestAPI.Content.getLibrary( @@ -6159,7 +6228,7 @@ describe('Content', () => { true, false, false, - privacy !== 'private', + privacy !== PRIVATE, () => { // Remove permission for Back-end team manager and OAE Team permissions = {}; @@ -6189,7 +6258,7 @@ describe('Content', () => { false, false, privacy !== - 'private', + PRIVATE, () => { checkPieceOfContent( contexts.simon @@ -6201,7 +6270,7 @@ describe('Content', () => { false, true, privacy !== - 'private', + PRIVATE, () => { checkPieceOfContent( contexts @@ -6213,11 +6282,11 @@ describe('Content', () => { .id, contentObj, privacy !== - 'private', + PRIVATE, false, false, privacy !== - 'private', + PRIVATE, callback ); } @@ -6279,7 +6348,7 @@ describe('Content', () => { it('verify public content group access', callback => { setUpUsers(contexts => { setUpGroups(contexts, groups => { - testGroupAccess(contexts, groups, 'public', callback); + testGroupAccess(contexts, groups, PUBLIC, callback); }); }); }); @@ -6290,7 +6359,7 @@ describe('Content', () => { it('verify logged in content group access', callback => { setUpUsers(contexts => { setUpGroups(contexts, groups => { - testGroupAccess(contexts, groups, 'loggedin', callback); + testGroupAccess(contexts, groups, LOGGEDIN, callback); }); }); }); @@ -6301,7 +6370,7 @@ describe('Content', () => { it('verify private content group access', callback => { setUpUsers(contexts => { setUpGroups(contexts, groups => { - testGroupAccess(contexts, groups, 'private', callback); + testGroupAccess(contexts, groups, PRIVATE, callback); }); }); }); diff --git a/packages/oae-lti/lib/api.js b/packages/oae-lti/lib/api.js index 655e7ed863..47a927ea5d 100644 --- a/packages/oae-lti/lib/api.js +++ b/packages/oae-lti/lib/api.js @@ -43,13 +43,16 @@ const getLtiTool = function(ctx, id, groupId, callback) { if (err) { return callback(err); } + if (!group.isManager && !group.isMember) { return callback({ code: 401, msg: 'The current user does not have access to this LTI tool' }); } + PrincipalsApi.getMe(ctx, (err, principal) => { if (err) { return callback(err); } + VersionAPI.getVersion((err, version) => { if (err) { log().warn('Failed to fetch OAE version'); @@ -59,6 +62,7 @@ const getLtiTool = function(ctx, id, groupId, callback) { } }; } + LtiDAO.getLtiTool(id, groupId, (err, tool) => { if (err) { log().error( @@ -83,13 +87,7 @@ const getLtiTool = function(ctx, id, groupId, callback) { ); // eslint-disable-next-line camelcase - launchParams.oauth_signature = oauth.hmacsign( - 'POST', - tool.launchUrl, - launchParams, - tool.secret, - '' - ); + launchParams.oauth_signature = oauth.hmacsign('POST', tool.launchUrl, launchParams, tool.secret, ''); // Scrub out OAUTH parameters from tool delete tool.secret; @@ -126,13 +124,16 @@ const addLtiTool = function(ctx, groupId, launchUrl, secret, consumerKey, opts, if (err) { return callback(err); } + if (group.deleted) { return callback({ code: 404, msg: util.format("Couldn't find group: %s", groupId) }); } + PrincipalsApi.getMe(ctx, (err, me) => { if (err) { return callback(err); } + if (!me.isTenantAdmin && !me.isGlobalAdmin) { return callback({ code: 401, @@ -148,9 +149,7 @@ const addLtiTool = function(ctx, groupId, launchUrl, secret, consumerKey, opts, // Parameter validation const validator = new Validator(); - validator - .check(groupId, { code: 400, msg: 'A valid group id must be provided' }) - .isGroupId(); + validator.check(groupId, { code: 400, msg: 'A valid group id must be provided' }).isGroupId(); validator .check(launchUrl, { code: 400, @@ -202,6 +201,7 @@ const addLtiTool = function(ctx, groupId, launchUrl, secret, consumerKey, opts, ); return callback(err); } + return callback(null, tool); } ); @@ -225,9 +225,11 @@ const getLtiTools = function(ctx, groupId, callback) { if (err) { return callback(err); } + if (group.deleted) { return callback({ code: 404, msg: util.format("Couldn't find group: %s", groupId) }); } + LtiDAO.getLtiToolsByGroupId(groupId, (err, tools) => { if (err) { log().error( @@ -238,6 +240,7 @@ const getLtiTools = function(ctx, groupId, callback) { ); return callback(err); } + return callback(null, tools); }); }); @@ -258,9 +261,11 @@ const deleteLtiTool = function(ctx, id, groupId, callback) { if (err) { return callback(err); } + if (group.deleted) { return callback({ code: 404, msg: util.format("Couldn't find group: %s", groupId) }); } + // Check if we can delete tools in this group AuthzPermissions.canManage(ctx, group, err => { if (err) { @@ -277,6 +282,7 @@ const deleteLtiTool = function(ctx, id, groupId, callback) { ); return callback(err); } + return callback(); }); }); diff --git a/packages/oae-lti/tests/test-lti.js b/packages/oae-lti/tests/test-lti.js index ad7fa41264..e610917ea5 100644 --- a/packages/oae-lti/tests/test-lti.js +++ b/packages/oae-lti/tests/test-lti.js @@ -53,217 +53,190 @@ describe('LTI tools', () => { * Test that verifies that LTI tool creation is successful when all of the parameters have been provided */ it('verify that LTI tool creation succeeds given a valid request', callback => { - RestAPI.Group.createGroup( - camAdminRestContext, - 'This is a group', - null, - 'public', - 'yes', - [], - [], - (err, group) => { - assert.ok(!err); - const launchUrl = 'http://lti.launch.url'; - const secret = 'secret'; - const key = '12345'; - RestAPI.LtiTool.createLtiTool( - camAdminRestContext, - group.id, - launchUrl, - secret, - key, - 'LTI tool title', - 'LTI tool description', - (err, ltiTool) => { - assert.ok(!err); - assert.strictEqual(ltiTool.groupId, group.id); - assert.strictEqual(ltiTool.launchUrl, launchUrl); - assert.strictEqual(ltiTool.displayName, 'LTI tool title'); - assert.strictEqual(ltiTool.description, 'LTI tool description'); - return callback(); - } - ); - } - ); + RestAPI.Group.createGroup(camAdminRestContext, 'This is a group', null, 'public', 'yes', [], [], (err, group) => { + assert.ok(!err); + const launchUrl = 'http://lti.launch.url'; + const secret = 'secret'; + const key = '12345'; + RestAPI.LtiTool.createLtiTool( + camAdminRestContext, + group.id, + launchUrl, + secret, + key, + 'LTI tool title', + 'LTI tool description', + (err, ltiTool) => { + assert.ok(!err); + assert.strictEqual(ltiTool.groupId, group.id); + assert.strictEqual(ltiTool.launchUrl, launchUrl); + assert.strictEqual(ltiTool.displayName, 'LTI tool title'); + assert.strictEqual(ltiTool.description, 'LTI tool description'); + return callback(); + } + ); + }); }); /** * Test that verifies that a LTI tool can be created without a description */ it('verify that missing description is accepted', callback => { - RestAPI.Group.createGroup( - camAdminRestContext, - 'This is a group', - null, - 'public', - 'yes', - [], - [], - (err, group) => { - assert.ok(!err); - const launchUrl = 'http://lti.launch.url'; - const secret = 'secret'; - const key = '12345'; - RestAPI.LtiTool.createLtiTool( - camAdminRestContext, - group.id, - launchUrl, - secret, - key, - 'LTI tool title', - null, - (err, toolObject) => { - assert.ok(!err); - assert.strictEqual(toolObject.description, ''); + RestAPI.Group.createGroup(camAdminRestContext, 'This is a group', null, 'public', 'yes', [], [], (err, group) => { + assert.ok(!err); + const launchUrl = 'http://lti.launch.url'; + const secret = 'secret'; + const key = '12345'; + RestAPI.LtiTool.createLtiTool( + camAdminRestContext, + group.id, + launchUrl, + secret, + key, + 'LTI tool title', + null, + (err, toolObject) => { + assert.ok(!err); + assert.strictEqual(toolObject.description, ''); - // Verify that an empty description is acceptable as well - RestAPI.LtiTool.createLtiTool( - camAdminRestContext, - group.id, - launchUrl, - secret, - key, - 'LTI tool title', - '', - (err, toolObject) => { - assert.ok(!err); - assert.strictEqual(toolObject.description, ''); - return callback(); - } - ); - } - ); - } - ); + // Verify that an empty description is acceptable as well + RestAPI.LtiTool.createLtiTool( + camAdminRestContext, + group.id, + launchUrl, + secret, + key, + 'LTI tool title', + '', + (err, toolObject) => { + assert.ok(!err); + assert.strictEqual(toolObject.description, ''); + return callback(); + } + ); + } + ); + }); }); /** * Test that verifies that creating a LTI tool with no launchUrl is not possible */ it('verify that missing launchUrl is not accepted', callback => { - RestAPI.Group.createGroup( - camAdminRestContext, - 'This is a group', - null, - 'public', - 'yes', - [], - [], - (err, group) => { - assert.ok(!err); - const secret = 'secret'; - const key = '12345'; - RestAPI.LtiTool.createLtiTool( - camAdminRestContext, - group.id, - '', - secret, - key, - 'LTI tool title', - null, - (err, toolObject) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.strictEqual(err.msg, 'You need to provide a launch URL for this LTI tool'); - return callback(); - } - ); - } - ); + RestAPI.Group.createGroup(camAdminRestContext, 'This is a group', null, 'public', 'yes', [], [], (err, group) => { + assert.ok(!err); + const secret = 'secret'; + const key = '12345'; + RestAPI.LtiTool.createLtiTool( + camAdminRestContext, + group.id, + '', + secret, + key, + 'LTI tool title', + null, + (err, toolObject) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.strictEqual(err.msg, 'You need to provide a launch URL for this LTI tool'); + return callback(); + } + ); + }); }); /** * Test that verifies that creating a LTI tool with no OAUTH secret is not possible */ it('verify that missing OAUTH secret is not accepted', callback => { - RestAPI.Group.createGroup( - camAdminRestContext, - 'This is a group', - null, - 'public', - 'yes', - [], - [], - (err, group) => { - assert.ok(!err); - const launchUrl = 'http://lti.launch.url'; - const key = '12345'; - RestAPI.LtiTool.createLtiTool( - camAdminRestContext, - group.id, - launchUrl, - null, - key, - 'LTI tool title', - null, - (err, toolObject) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.strictEqual(err.msg, 'You need to provide an OAUTH secret for this LTI tool'); - return callback(); - } - ); - } - ); + RestAPI.Group.createGroup(camAdminRestContext, 'This is a group', null, 'public', 'yes', [], [], (err, group) => { + assert.ok(!err); + const launchUrl = 'http://lti.launch.url'; + const key = '12345'; + RestAPI.LtiTool.createLtiTool( + camAdminRestContext, + group.id, + launchUrl, + null, + key, + 'LTI tool title', + null, + (err, toolObject) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.strictEqual(err.msg, 'You need to provide an OAUTH secret for this LTI tool'); + return callback(); + } + ); + }); }); /** * Test that verifies that creating a LTI tool with no OAUTH consumer key is not possible */ it('verify that missing OAUTH consumer key is not accepted', callback => { - RestAPI.Group.createGroup( - camAdminRestContext, - 'This is a group', - null, - 'public', - 'yes', - [], - [], - (err, group) => { - assert.ok(!err); - const launchUrl = 'http://lti.launch.url'; - const secret = 'secret'; - RestAPI.LtiTool.createLtiTool( - camAdminRestContext, - group.id, - launchUrl, - secret, - null, - 'LTI tool title', - null, - (err, toolObject) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.strictEqual( - err.msg, - 'You need to provide an OAUTH consumer key for this LTI tool' - ); - return callback(); - } - ); - } - ); + RestAPI.Group.createGroup(camAdminRestContext, 'This is a group', null, 'public', 'yes', [], [], (err, group) => { + assert.ok(!err); + const launchUrl = 'http://lti.launch.url'; + const secret = 'secret'; + RestAPI.LtiTool.createLtiTool( + camAdminRestContext, + group.id, + launchUrl, + secret, + null, + 'LTI tool title', + null, + (err, toolObject) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.strictEqual(err.msg, 'You need to provide an OAUTH consumer key for this LTI tool'); + return callback(); + } + ); + }); }); /** * Test that verifies that a non-manager of a group can not create a LTI tool */ it('verify that a non-manager can not create LTI tool', callback => { - RestAPI.Group.createGroup( - camAdminRestContext, - 'This is a group', - null, - 'public', - 'yes', - [], - [], - (err, group) => { + RestAPI.Group.createGroup(camAdminRestContext, 'This is a group', null, 'public', 'yes', [], [], (err, group) => { + assert.ok(!err); + const launchUrl = 'http://lti.launch.url'; + const secret = 'secret'; + const key = '12345'; + RestAPI.LtiTool.createLtiTool( + anonymousRestContext, + group.id, + launchUrl, + secret, + key, + 'LTI tool title', + 'LTI tool description', + (err, toolObject) => { + assert.ok(err); + assert.strictEqual(err.code, 401); + assert.strictEqual(err.msg, 'The current user is not authorized to create an LTI tool'); + return callback(); + } + ); + }); + }); + + /** + * Test that verifies LTI tools can not be created in deleted groups + */ + it('verify that a LTI tool can not be created in a deleted group', callback => { + RestAPI.Group.createGroup(camAdminRestContext, 'This is a group', null, 'public', 'yes', [], [], (err, group) => { + assert.ok(!err); + RestAPI.Group.deleteGroup(camAdminRestContext, group.id, err => { assert.ok(!err); const launchUrl = 'http://lti.launch.url'; const secret = 'secret'; const key = '12345'; RestAPI.LtiTool.createLtiTool( - anonymousRestContext, + camAdminRestContext, group.id, launchUrl, secret, @@ -272,55 +245,13 @@ describe('LTI tools', () => { 'LTI tool description', (err, toolObject) => { assert.ok(err); - assert.strictEqual(err.code, 401); - assert.strictEqual( - err.msg, - 'The current user is not authorized to create an LTI tool' - ); + assert.strictEqual(err.code, 404); + assert.strictEqual(err.msg, "Couldn't find group: " + group.id); return callback(); } ); - } - ); - }); - - /** - * Test that verifies LTI tools can not be created in deleted groups - */ - it('verify that a LTI tool can not be created in a deleted group', callback => { - RestAPI.Group.createGroup( - camAdminRestContext, - 'This is a group', - null, - 'public', - 'yes', - [], - [], - (err, group) => { - assert.ok(!err); - RestAPI.Group.deleteGroup(camAdminRestContext, group.id, err => { - assert.ok(!err); - const launchUrl = 'http://lti.launch.url'; - const secret = 'secret'; - const key = '12345'; - RestAPI.LtiTool.createLtiTool( - camAdminRestContext, - group.id, - launchUrl, - secret, - key, - 'LTI tool title', - 'LTI tool description', - (err, toolObject) => { - assert.ok(err); - assert.strictEqual(err.code, 404); - assert.strictEqual(err.msg, "Couldn't find group: " + group.id); - return callback(); - } - ); - }); - } - ); + }); + }); }); }); @@ -330,60 +261,45 @@ describe('LTI tools', () => { * created */ it('verify retrieved LTI tool launch data', callback => { - RestAPI.Group.createGroup( - camAdminRestContext, - 'This is a group', - null, - 'public', - 'yes', - [], - [], - (err, group) => { - const launchUrl = 'http://lti.launch.url'; - const secret = 'secret'; - const key = '12345'; - const title = 'LTI tool title'; - const description = 'LTI tool description'; - RestAPI.LtiTool.createLtiTool( - camAdminRestContext, - group.id, - launchUrl, - secret, - key, - title, - description, - (err, tool) => { + RestAPI.Group.createGroup(camAdminRestContext, 'This is a group', null, 'public', 'yes', [], [], (err, group) => { + const launchUrl = 'http://lti.launch.url'; + const secret = 'secret'; + const key = '12345'; + const title = 'LTI tool title'; + const description = 'LTI tool description'; + RestAPI.LtiTool.createLtiTool( + camAdminRestContext, + group.id, + launchUrl, + secret, + key, + title, + description, + (err, tool) => { + assert.ok(!err); + RestAPI.Group.joinGroup(janeRestContext, group.id, err => { assert.ok(!err); - RestAPI.Group.joinGroup(janeRestContext, group.id, err => { + // Get the LTI tool and verify its model + RestAPI.LtiTool.getLtiTool(janeRestContext, group.id, tool.id, (err, data) => { assert.ok(!err); - // Get the LTI tool and verify its model - RestAPI.LtiTool.getLtiTool(janeRestContext, group.id, tool.id, (err, data) => { - assert.ok(!err); - const ltiLaunchData = data.launchParams; - assert.strictEqual(ltiLaunchData.oauth_consumer_key, key); - assert.strictEqual(ltiLaunchData.lti_message_type, 'basic-lti-launch-request'); - assert.strictEqual(ltiLaunchData.lti_version, 'LTI-1p0'); - assert.strictEqual(ltiLaunchData.tool_consumer_info_product_family_code, 'OAE'); - assert.strictEqual(ltiLaunchData.resource_link_id, tool.id); - assert.strictEqual(ltiLaunchData.resource_link_title, title); - assert.strictEqual(ltiLaunchData.resource_link_description, description); - assert.strictEqual( - ltiLaunchData.user_id, - group.id + ':' + janeRestContext.user.id - ); - assert.strictEqual(ltiLaunchData.context_id, group.id); - assert.strictEqual( - ltiLaunchData.lis_person_email_primary, - janeRestContext.user.email - ); - assert.strictEqual(ltiLaunchData.roles, 'Learner'); - return callback(); - }); + const ltiLaunchData = data.launchParams; + assert.strictEqual(ltiLaunchData.oauth_consumer_key, key); + assert.strictEqual(ltiLaunchData.lti_message_type, 'basic-lti-launch-request'); + assert.strictEqual(ltiLaunchData.lti_version, 'LTI-1p0'); + assert.strictEqual(ltiLaunchData.tool_consumer_info_product_family_code, 'OAE'); + assert.strictEqual(ltiLaunchData.resource_link_id, tool.id); + assert.strictEqual(ltiLaunchData.resource_link_title, title); + assert.strictEqual(ltiLaunchData.resource_link_description, description); + assert.strictEqual(ltiLaunchData.user_id, group.id + ':' + janeRestContext.user.id); + assert.strictEqual(ltiLaunchData.context_id, group.id); + assert.strictEqual(ltiLaunchData.lis_person_email_primary, janeRestContext.user.email); + assert.strictEqual(ltiLaunchData.roles, 'Learner'); + return callback(); }); - } - ); - } - ); + }); + } + ); + }); }); /** @@ -391,40 +307,30 @@ describe('LTI tools', () => { */ it('verify non existing LTI tool can not be retrieved', callback => { // Invalid group identifier - RestAPI.LtiTool.getLtiTool( - camAdminRestContext, - 'g:camtest:not-exists', - '12345', - (err, ltiTool) => { - assert.ok(err); - assert.strictEqual(err.code, 404); - assert.ok(!ltiTool); + RestAPI.LtiTool.getLtiTool(camAdminRestContext, 'g:camtest:not-exists', '12345', (err, ltiTool) => { + assert.ok(err); + assert.strictEqual(err.code, 404); + assert.ok(!ltiTool); - RestAPI.Group.createGroup( - camAdminRestContext, - 'This is a group', - null, - 'public', - 'yes', - [], - [], - (err, group) => { - // Non existing tool - RestAPI.LtiTool.getLtiTool( - camAdminRestContext, - group.id, - 'not-a-tool', - (err, ltiTool) => { - assert.ok(err); - assert.strictEqual(err.code, 404); - assert.ok(!ltiTool); - return callback(); - } - ); - } - ); - } - ); + RestAPI.Group.createGroup( + camAdminRestContext, + 'This is a group', + null, + 'public', + 'yes', + [], + [], + (err, group) => { + // Non existing tool + RestAPI.LtiTool.getLtiTool(camAdminRestContext, group.id, 'not-a-tool', (err, ltiTool) => { + assert.ok(err); + assert.strictEqual(err.code, 404); + assert.ok(!ltiTool); + return callback(); + }); + } + ); + }); }); }); @@ -433,57 +339,48 @@ describe('LTI tools', () => { * Test that verifies that all LTI tools linked to a group can be successfully retrieved */ it('verify retrieving LTI tools for a group', callback => { - RestAPI.Group.createGroup( - camAdminRestContext, - 'This is a group', - null, - 'public', - 'yes', - [], - [], - (err, group) => { - const secret = 'secret'; - const title = 'LTI tool title'; - const description = 'LTI tool description'; - RestAPI.LtiTool.createLtiTool( - camAdminRestContext, - group.id, - 'http://lti.launch1.url', - secret, - '12345', - title, - description, - (err, tool1) => { - assert.ok(!err); - RestAPI.LtiTool.createLtiTool( - camAdminRestContext, - group.id, - 'http://lti.launch2.url', - secret, - '12346', - title, - description, - (err, tool2) => { + RestAPI.Group.createGroup(camAdminRestContext, 'This is a group', null, 'public', 'yes', [], [], (err, group) => { + const secret = 'secret'; + const title = 'LTI tool title'; + const description = 'LTI tool description'; + RestAPI.LtiTool.createLtiTool( + camAdminRestContext, + group.id, + 'http://lti.launch1.url', + secret, + '12345', + title, + description, + (err, tool1) => { + assert.ok(!err); + RestAPI.LtiTool.createLtiTool( + camAdminRestContext, + group.id, + 'http://lti.launch2.url', + secret, + '12346', + title, + description, + (err, tool2) => { + assert.ok(!err); + // Get the LTI tools for the group + RestAPI.LtiTool.getLtiTools(camAdminRestContext, group.id, (err, ltiTools) => { assert.ok(!err); - // Get the LTI tools for the group - RestAPI.LtiTool.getLtiTools(camAdminRestContext, group.id, (err, ltiTools) => { - assert.ok(!err); - assert.strictEqual(ltiTools.results.length, 2); - const tool = ltiTools.results[0]; - assert.strictEqual(tool.groupId, group.id); - // Check that OAUTH secret is not included in the returned object - assert.ok(!tool.secret); - const ids = _.pluck(ltiTools.results, 'id'); - assert.ok(_.contains(ids, tool1.id)); - assert.ok(_.contains(ids, tool2.id)); - return callback(); - }); - } - ); - } - ); - } - ); + assert.strictEqual(ltiTools.results.length, 2); + const tool = ltiTools.results[0]; + assert.strictEqual(tool.groupId, group.id); + // Check that OAUTH secret is not included in the returned object + assert.ok(!tool.secret); + const ids = _.pluck(ltiTools.results, 'id'); + assert.ok(_.contains(ids, tool1.id)); + assert.ok(_.contains(ids, tool2.id)); + return callback(); + }); + } + ); + } + ); + }); }); /** @@ -531,36 +428,28 @@ describe('LTI tools', () => { assert.ok(!err); RestAPI.Group.deleteGroup(camAdminRestContext, group.id, err => { assert.ok(!err); - RestAPI.LtiTool.getLtiTools( - camAdminRestContext, - group.id, - (err, ltiTools) => { - assert.ok(err); - assert.strictEqual(err.code, 404); - assert.strictEqual(err.msg, "Couldn't find group: " + group.id); - // Test that an empty array is returned for a group with no tools - RestAPI.Group.createGroup( - camAdminRestContext, - 'This is a group', - null, - 'public', - 'yes', - [], - [], - (err, group) => { - RestAPI.LtiTool.getLtiTools( - camAdminRestContext, - group.id, - (err, ltiTools) => { - assert.ok(!err); - assert.strictEqual(ltiTools.results.length, 0); - return callback(); - } - ); - } - ); - } - ); + RestAPI.LtiTool.getLtiTools(camAdminRestContext, group.id, (err, ltiTools) => { + assert.ok(err); + assert.strictEqual(err.code, 404); + assert.strictEqual(err.msg, "Couldn't find group: " + group.id); + // Test that an empty array is returned for a group with no tools + RestAPI.Group.createGroup( + camAdminRestContext, + 'This is a group', + null, + 'public', + 'yes', + [], + [], + (err, group) => { + RestAPI.LtiTool.getLtiTools(camAdminRestContext, group.id, (err, ltiTools) => { + assert.ok(!err); + assert.strictEqual(ltiTools.results.length, 0); + return callback(); + }); + } + ); + }); }); } ); @@ -577,135 +466,102 @@ describe('LTI tools', () => { * Test that verifies that LTI tools can be deleted */ it('verify that LTI tools can be deleted', callback => { - RestAPI.Group.createGroup( - camAdminRestContext, - 'This is a group', - null, - 'public', - 'yes', - [], - [], - (err, group) => { - assert.ok(!err); - const launchUrl = 'http://lti.launch.url'; - const secret = 'secret'; - const key = '12345'; - RestAPI.LtiTool.createLtiTool( - camAdminRestContext, - group.id, - launchUrl, - secret, - key, - 'LTI tool title', - 'LTI tool description', - (err, tool) => { + RestAPI.Group.createGroup(camAdminRestContext, 'This is a group', null, 'public', 'yes', [], [], (err, group) => { + assert.ok(!err); + const launchUrl = 'http://lti.launch.url'; + const secret = 'secret'; + const key = '12345'; + RestAPI.LtiTool.createLtiTool( + camAdminRestContext, + group.id, + launchUrl, + secret, + key, + 'LTI tool title', + 'LTI tool description', + (err, tool) => { + assert.ok(!err); + const { id } = tool; + // Assert the tool exists and can be fetched + RestAPI.LtiTool.getLtiTool(camAdminRestContext, group.id, id, (err, data) => { assert.ok(!err); - const { id } = tool; - // Assert the tool exists and can be fetched - RestAPI.LtiTool.getLtiTool(camAdminRestContext, group.id, id, (err, data) => { + const ltiLaunchData = data.launchParams; + assert.strictEqual(ltiLaunchData.oauth_consumer_key, key); + assert.strictEqual(ltiLaunchData.resource_link_id, id); + RestAPI.LtiTool.deleteLtiTool(camAdminRestContext, group.id, id, err => { assert.ok(!err); - const ltiLaunchData = data.launchParams; - assert.strictEqual(ltiLaunchData.oauth_consumer_key, key); - assert.strictEqual(ltiLaunchData.resource_link_id, id); - RestAPI.LtiTool.deleteLtiTool(camAdminRestContext, group.id, id, err => { - assert.ok(!err); - // Assert the tool can no longer be fetched - RestAPI.LtiTool.getLtiTool(camAdminRestContext, group.id, id, (err, data) => { - assert.ok(err); - assert.strictEqual(err.code, 404); - assert.strictEqual( - err.msg, - util.format('Could not find LTI tool %s for group %s', id, group.id) - ); - return callback(); - }); + // Assert the tool can no longer be fetched + RestAPI.LtiTool.getLtiTool(camAdminRestContext, group.id, id, (err, data) => { + assert.ok(err); + assert.strictEqual(err.code, 404); + assert.strictEqual(err.msg, util.format('Could not find LTI tool %s for group %s', id, group.id)); + return callback(); }); }); - } - ); - } - ); + }); + } + ); + }); }); /** * Test that verifies that a non-manager of a group can not delete a LTI tool */ it('verify that a non-manager can not delete LTI tool', callback => { - RestAPI.Group.createGroup( - camAdminRestContext, - 'This is a group', - null, - 'public', - 'yes', - [], - [], - (err, group) => { - assert.ok(!err); - const launchUrl = 'http://lti.launch.url'; - const secret = 'secret'; - const key = '12345'; - RestAPI.LtiTool.createLtiTool( - camAdminRestContext, - group.id, - launchUrl, - secret, - key, - 'LTI tool title', - 'LTI tool description', - (err, tool) => { - assert.ok(!err); - RestAPI.LtiTool.deleteLtiTool(anonymousRestContext, group.id, tool.id, err => { - assert.ok(err); - assert.strictEqual(err.code, 401); - assert.strictEqual( - err.msg, - 'The current user does not have access to manage this resource' - ); - return callback(); - }); - } - ); - } - ); + RestAPI.Group.createGroup(camAdminRestContext, 'This is a group', null, 'public', 'yes', [], [], (err, group) => { + assert.ok(!err); + const launchUrl = 'http://lti.launch.url'; + const secret = 'secret'; + const key = '12345'; + RestAPI.LtiTool.createLtiTool( + camAdminRestContext, + group.id, + launchUrl, + secret, + key, + 'LTI tool title', + 'LTI tool description', + (err, tool) => { + assert.ok(!err); + RestAPI.LtiTool.deleteLtiTool(anonymousRestContext, group.id, tool.id, err => { + assert.ok(err); + assert.strictEqual(err.code, 401); + assert.strictEqual(err.msg, 'The current user does not have access to manage this resource'); + return callback(); + }); + } + ); + }); }); /** * Test that verifies LTI tools can not be deleted in deleted groups */ it('verify that a LTI tool can not be deleted in a deleted group', callback => { - RestAPI.Group.createGroup( - camAdminRestContext, - 'This is a group', - null, - 'public', - 'yes', - [], - [], - (err, group) => { - assert.ok(!err); - RestAPI.LtiTool.createLtiTool( - camAdminRestContext, - group.id, - 'http://lti.launch.url', - 'secret', - '12345', - 'LTI tool title', - 'LTI tool description', - (err, tool) => { + RestAPI.Group.createGroup(camAdminRestContext, 'This is a group', null, 'public', 'yes', [], [], (err, group) => { + assert.ok(!err); + RestAPI.LtiTool.createLtiTool( + camAdminRestContext, + group.id, + 'http://lti.launch.url', + 'secret', + '12345', + 'LTI tool title', + 'LTI tool description', + (err, tool) => { + assert.ok(!err); + RestAPI.Group.deleteGroup(camAdminRestContext, group.id, err => { assert.ok(!err); - RestAPI.Group.deleteGroup(camAdminRestContext, group.id, err => { - assert.ok(!err); - RestAPI.LtiTool.deleteLtiTool(camAdminRestContext, group.id, tool.id, err => { - assert.ok(err); - assert.strictEqual(err.code, 404); - assert.strictEqual(err.msg, util.format("Couldn't find group: %s", group.id)); - return callback(); - }); + RestAPI.LtiTool.deleteLtiTool(camAdminRestContext, group.id, tool.id, err => { + assert.ok(err); + assert.strictEqual(err.code, 404); + assert.strictEqual(err.msg, util.format("Couldn't find group: %s", group.id)); + return callback(); }); - } - ); - } - ); + }); + } + ); + }); }); }); }); diff --git a/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js b/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js index 203236b6a7..5c047d83e6 100644 --- a/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js +++ b/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js @@ -33,6 +33,8 @@ const screenShottingOptions = { height: PreviewConstants.SIZES.IMAGE.WIDE_HEIGHT } }; +const COLLABDOC = 'collabdoc'; +const COLLABSHEET = 'collabsheet'; /** * Initializes the CollabDocProcessor @@ -45,10 +47,7 @@ const screenShottingOptions = { const init = function(_config, callback) { _config = _config || {}; - screenShottingOptions.timeout = OaeUtil.getNumberParam( - _config.screenShotting.timeout, - screenShottingOptions.timeout - ); + screenShottingOptions.timeout = OaeUtil.getNumberParam(_config.screenShotting.timeout, screenShottingOptions.timeout); const chromiumExecutable = _config.screenShotting.binary; if (chromiumExecutable) { @@ -71,7 +70,7 @@ const FILE_URI = 'file://'; * @borrows Interface.test as CollabDocProcessor.test */ const test = function(ctx, contentObj, callback) { - if (contentObj.resourceSubType === 'collabdoc') { + if (contentObj.resourceSubType === COLLABDOC || contentObj.resourceSubType === COLLABSHEET) { callback(null, 10); } else { callback(null, -1); @@ -88,23 +87,28 @@ const generatePreviews = function(ctx, contentObj, callback) { if (err) { return callback(err); } + if (revisions.results.length === 1) { // Only 1 revision => unpublished document. // Ignore it for now. return callback(null, true); } - // Write the etherpad HTML to an HTML file, so a screenshot can be generated as the preview - _writeEtherpadHtml(ctx, contentObj.latestRevision.etherpadHtml, (err, etherpadFilePath) => { + // Store whether this document is a collaborative document or spreadsheet + const type = contentObj.resourceSubType; + const html = type === COLLABDOC ? 'etherpadHtml' : 'ethercalcHtml'; + + // Write the HTML to an HTML file, so a screenshot can be generated as the preview + _writeCollabHtml(ctx, contentObj.latestRevision[html], type, function(err, collabFilePath) { if (err) { return callback(err); } // Generate a screenshot that is suitable to display in the activity feed. - etherpadFilePath = FILE_URI + etherpadFilePath; + const collabFileUri = FILE_URI + collabFilePath; const imgPath = Path.join(ctx.baseDir, '/wide.png'); - puppeteerHelper.getImage(etherpadFilePath, imgPath, screenShottingOptions, err => { + puppeteerHelper.getImage(collabFileUri, imgPath, screenShottingOptions, err => { if (err) { log().error({ err, contentId: ctx.contentId }, 'Could not generate an image'); return callback(err); @@ -136,8 +140,7 @@ const generatePreviews = function(ctx, contentObj, callback) { } // Move the files to the thumbnail path - const key = - PreviewConstants.SIZES.IMAGE.THUMBNAIL + 'x' + PreviewConstants.SIZES.IMAGE.THUMBNAIL; + const key = PreviewConstants.SIZES.IMAGE.THUMBNAIL + 'x' + PreviewConstants.SIZES.IMAGE.THUMBNAIL; const thumbnailPath = Path.join(ctx.baseDir, '/thumbnail.png'); IO.moveFile(files[key].path, thumbnailPath, err => { @@ -156,36 +159,37 @@ const generatePreviews = function(ctx, contentObj, callback) { }; /** - * Take the provided Etherpad HTML, wrap into the preview template and store it to disk + * Take the provided Collab HTML, wrap into the preview template and store it to disk * * @param {Context} ctx Standard context object containing the current user and the current tenant - * @param {String} etherpadHtml The HTML to wrap into the preview template + * @param {String} collabHtml The HTML to wrap into the preview template * @param {Function} callback Standard callback function * @param {Object} callback.err An error that occurred, if any * @param {String} callback.path The path the file has been written to * @api private */ -const _writeEtherpadHtml = function(ctx, etherpadHtml, callback) { - _getWrappedEtherpadHtml(ctx, etherpadHtml, (err, wrappedHtml) => { +const _writeCollabHtml = function(ctx, collabHtml, type, callback) { + _getWrappedCollabHtml(ctx, collabHtml, (err, wrappedHtml) => { if (err) { return callback(err); } - // Write the resulting HTML file to a temporary file on disk - const etherpadFilePath = Path.join(ctx.baseDir, 'etherpad.html'); - fs.writeFile(etherpadFilePath, wrappedHtml, err => { + // Write the resulting HTML to a temporary file on disk + const collabFilePath = + type === COLLABDOC ? Path.join(ctx.baseDir, '/etherpad.html') : Path.join(ctx.baseDir, '/ethercalc.html'); + fs.writeFile(collabFilePath, wrappedHtml, err => { if (err) { - log().error({ err, contentId: ctx.contentId }, 'Could not write the etherpad HTML to disk'); - return callback({ code: 500, msg: 'Could not write the etherpad HTML to disk' }); + log().error({ err, contentId: ctx.contentId }, 'Could not write the collaborative file preview HTML to disk'); + return callback({ code: 500, msg: 'Could not write the collaborative file preview HTML to disk' }); } - return callback(null, etherpadFilePath); + return callback(null, collabFilePath); }); }); }; /** - * Wrap provided etherpad HTML into an HTML template + * Wrap provided HTML into an HTML template * * @param {Context} ctx Standard context object containing the current user and the current tenant * @param {String} etherpadHtml The HTML as retrieved from the API @@ -194,16 +198,16 @@ const _writeEtherpadHtml = function(ctx, etherpadHtml, callback) { * @param {String} callback.html Resulting wrapped HTML * @api private */ -const _getWrappedEtherpadHtml = function(ctx, etherpadHtml, callback) { +const _getWrappedCollabHtml = function(ctx, collabHtml, callback) { let htmlFragment = null; // Extract the body from the HTML fragment try { - const $ = cheerio.load(etherpadHtml); + const $ = cheerio.load(collabHtml); htmlFragment = $('body').html() || ''; } catch (error) { log().error( - { err: error, etherpadHtml, contentId: ctx.contentId }, - 'Unable to parse etherpad HTML' + { err: error, collabHtml, contentId: ctx.contentId }, + 'Unable to parse collaborative file preview HTML' ); return callback({ code: 500, msg: 'Unable to parse etherpad HTML' }); } @@ -221,8 +225,8 @@ const _getWrappedEtherpadHtml = function(ctx, etherpadHtml, callback) { } else { fs.readFile(HTML_FILE, 'utf8', (err, content) => { if (err) { - log().error({ err, contentId: ctx.contentId }, 'Could not read the collabdoc wrapper HTML'); - return callback({ code: 500, msg: 'Could not read the collabdoc wrapper HTML' }); + log().error({ err, contentId: ctx.contentId }, 'Could not read the collaborative file preview wrapper HTML'); + return callback({ code: 500, msg: 'Could not read the collaborative file preview wrapper HTML' }); } // Cache the wrapped HTML diff --git a/packages/oae-preview-processor/lib/rest.js b/packages/oae-preview-processor/lib/rest.js index a7592af062..02fb1c46da 100644 --- a/packages/oae-preview-processor/lib/rest.js +++ b/packages/oae-preview-processor/lib/rest.js @@ -29,7 +29,7 @@ const PreviewProcessorAPI = require('oae-preview-processor'); * @Method POST * @Path /content/reprocessPreviews * @FormParam {String[]} [content_createdBy] Filter content based on who it was created by - * @FormParam {string[]} [content_resourceSubType] Filter content based on its resourceSubType [collabdoc,file,link] + * @FormParam {string[]} [content_resourceSubType] Filter content based on its resourceSubType [collabdoc,collabsheet,file,link] * @FormParam {string[]} [content_previewsStatus] Filter content based on the status of the previews processing [ignored,error] * @FormParam {string[]} [content_tenant] Filter content based on the tenant where it was created * @FormParam {number} [revision_createdAfter] Filter those revisions who were created after a certain timestamp in ms since epoch @@ -60,6 +60,7 @@ OAE.globalAdminRouter.on('post', '/api/content/reprocessPreviews', (req, res) => if (err) { return res.status(err.code).send(err.msg); } + res.status(200).end(); }); }); @@ -72,18 +73,13 @@ OAE.globalAdminRouter.on('post', '/api/content/reprocessPreviews', (req, res) => * @api private */ const _handleReprocessPreview = function(req, res) { - PreviewProcessorAPI.reprocessPreview( - req.ctx, - req.params.contentId, - req.params.revisionId, - err => { - if (err) { - return res.status(err.code).send(err.msg); - } - - res.status(200).end(); + PreviewProcessorAPI.reprocessPreview(req.ctx, req.params.contentId, req.params.revisionId, err => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(200).end(); + }); }; /** @@ -107,11 +103,7 @@ OAE.globalAdminRouter.on( '/api/content/:contentId/revision/:revisionId/reprocessPreview', _handleReprocessPreview ); -OAE.tenantRouter.on( - 'post', - '/api/content/:contentId/revision/:revisionId/reprocessPreview', - _handleReprocessPreview -); +OAE.tenantRouter.on('post', '/api/content/:contentId/revision/:revisionId/reprocessPreview', _handleReprocessPreview); /** * @REST getLongurlExpand diff --git a/packages/oae-search/lib/restmodel.js b/packages/oae-search/lib/restmodel.js index 95ade534ea..4aa1ee0304 100644 --- a/packages/oae-search/lib/restmodel.js +++ b/packages/oae-search/lib/restmodel.js @@ -23,7 +23,7 @@ * @Property {number} lastModified The timestamp (millis since epoch) at which the content item was last modified * @Property {string} mime The mime type of the content item * @Property {string} profilePath The relative path to the user profile - * @Property {string} resourceSubType The content item type [collabdoc,file,link] + * @Property {string} resourceSubType The content item type [collabdoc,collabsheet,file,link] * @Property {string} resourceType The resource type of the content item [content] * @Property {BasicTenant} tenant The tenant to which this content item is associated * @Property {string} tenantAlias The alias of the tenant to which this content item is associated diff --git a/packages/oae-tests/lib/util.js b/packages/oae-tests/lib/util.js index 5c803309aa..c8d3e97e59 100644 --- a/packages/oae-tests/lib/util.js +++ b/packages/oae-tests/lib/util.js @@ -61,7 +61,7 @@ const NOT_JOINABLE = 'no'; const JOINABLE = 'yes'; const JOINABLE_BY_REQUEST = 'request'; const PUBLIC = 'public'; -const LOGGED_IN = 'loggedin'; +const LOGGEDIN = 'loggedin'; const PRIVATE = 'private'; /** @@ -379,7 +379,7 @@ const generateTestGroups = function(restContext, total, callback, _groups) { restContext, generateTestGroupId('random-title'), generateTestGroupId('random-description'), - 'public', + PUBLIC, 'yes', [], [], @@ -633,7 +633,7 @@ const createGlobalAdminContext = function() { const globalTenant = global.oaeTests.tenants.global; const globalAdminId = 'u:' + globalTenant.alias + ':admin'; const globalUser = new User(globalTenant.alias, globalAdminId, 'Global Administrator', 'admin@example.com', { - visibility: 'private', + visibility: PRIVATE, isGlobalAdmin: true }); return new Context(globalTenant, globalUser); @@ -701,8 +701,10 @@ const generateRandomText = function(numberOfWords) { const letter = Math.floor(Math.random() * alphabet.length); word += alphabet[letter]; } + text.push(word); } + return text.join(' '); }; @@ -739,6 +741,7 @@ const createFileReadableStream = function(filename, size) { for (let i = 0; i < toGenerate; i++) { data += '0'; } + return data; }; @@ -825,12 +828,12 @@ const setupMultiTenantPrivacyEntities = function(callback) { * @api private */ const _createMultiPrivacyTenants = function(callback) { - const publicTenantAlias = TenantsTestUtil.generateTestTenantAlias('public'); + const publicTenantAlias = TenantsTestUtil.generateTestTenantAlias(PUBLIC); const publicTenant1Alias = TenantsTestUtil.generateTestTenantAlias('public1'); - const privateTenantAlias = TenantsTestUtil.generateTestTenantAlias('private'); + const privateTenantAlias = TenantsTestUtil.generateTestTenantAlias(PRIVATE); const privateTenant1Alias = TenantsTestUtil.generateTestTenantAlias('private1'); - _createPublicTenant(publicTenantAlias, 'public', (tenant, tenantAdmin) => { + _createPublicTenant(publicTenantAlias, PUBLIC, (tenant, tenantAdmin) => { const publicTenant = { tenant, adminUser: tenantAdmin, @@ -838,7 +841,7 @@ const _createMultiPrivacyTenants = function(callback) { anonymousRestContext: createTenantRestContext(tenant.host) }; - _createPublicTenant(publicTenant1Alias, 'public', (tenant, tenantAdmin) => { + _createPublicTenant(publicTenant1Alias, PUBLIC, (tenant, tenantAdmin) => { const publicTenant1 = { tenant, adminUser: tenantAdmin, @@ -913,9 +916,9 @@ const _setupTenant = function(tenant, callback) { * @api private */ const _createMultiPrivacyUsers = function(tenant, callback) { - _createUserWithVisibility(tenant, 'public', publicUser => { - _createUserWithVisibility(tenant, 'loggedin', loggedinUser => { - _createUserWithVisibility(tenant, 'private', privateUser => { + _createUserWithVisibility(tenant, PUBLIC, publicUser => { + _createUserWithVisibility(tenant, LOGGEDIN, loggedinUser => { + _createUserWithVisibility(tenant, PRIVATE, privateUser => { return callback(publicUser, loggedinUser, privateUser); }); }); @@ -985,7 +988,7 @@ const _getGroupsToBeCreated = function(tenant) { return { publicGroup: { visibility: PUBLIC, memberPrincipalId: tenant.publicUser.user.id, joinable: JOINABLE_BY_REQUEST }, loggedinJoinableGroupByRequest: { - visibility: LOGGED_IN, + visibility: LOGGEDIN, memberPrincipalId: tenant.loggedinUser.user.id, joinable: JOINABLE_BY_REQUEST }, @@ -995,7 +998,7 @@ const _getGroupsToBeCreated = function(tenant) { joinable: JOINABLE_BY_REQUEST }, loggedinNotJoinableGroup: { - visibility: LOGGED_IN, + visibility: LOGGEDIN, memberPrincipalId: tenant.loggedinUser.user.id, joinable: NOT_JOINABLE }, @@ -1005,7 +1008,7 @@ const _getGroupsToBeCreated = function(tenant) { joinable: NOT_JOINABLE }, loggedinJoinableGroup: { - visibility: LOGGED_IN, + visibility: LOGGEDIN, memberPrincipalId: tenant.loggedinUser.user.id, joinable: JOINABLE }, @@ -1057,7 +1060,7 @@ const _createGroupWithVisibility = function(tenant, group, callback) { * @api private */ const _createPrivateTenant = function(tenantAlias, callback) { - _createPublicTenant(tenantAlias, 'private', (tenant, tenantAdmin) => { + _createPublicTenant(tenantAlias, PRIVATE, (tenant, tenantAdmin) => { // Only global admins can update tenant privacy, so use that ConfigTestUtil.updateConfigAndWait( createGlobalAdminRestContext(), @@ -1250,6 +1253,7 @@ const setUpBeforeTests = function(config, dropKeyspaceBeforeTest, callback) { if (err) { return callback(new Error(err.msg)); } + // Run migrations otherwise keyspace is empty migrationRunner.runMigrations(config.cassandra, () => { Cassandra.close(() => { diff --git a/packages/oae-util/lib/cassandra.js b/packages/oae-util/lib/cassandra.js index eef6a2d94a..fe8650e360 100644 --- a/packages/oae-util/lib/cassandra.js +++ b/packages/oae-util/lib/cassandra.js @@ -58,6 +58,7 @@ const init = function(config, callback) { callback(err); }); } + client = _createNewClient(CONFIG.hosts, keyspace); callback(); }); @@ -164,10 +165,12 @@ const keyspaceExists = function(name, callback) { if (results.rowLength === 0) { return callback(null, false); } + if (err) { log().error({ err, name }, 'Error while describing cassandra keyspace'); callback({ code: 500, msg: 'Error while describing cassandra keyspace' }); } + return callback(null, true); }); }; @@ -204,6 +207,7 @@ const dropColumnFamily = function(name, callback) { if (err) { return callback(err); } + if (!exists) { return callback({ code: 400, @@ -254,6 +258,7 @@ const _dropColumnFamilies = function(families, callback) { if (err) { return callback(err); } + _dropColumnFamilies(families, callback); }); }; @@ -324,6 +329,7 @@ const _createColumnFamilies = function(keys, families, callback) { if (err) { return callback(err); } + _createColumnFamilies(keys, families, callback); }); }; @@ -404,6 +410,7 @@ const runBatchQuery = function(queries, callback) { if (err) { return callback(err); } + return callback(null, result.rows); }); }; @@ -472,6 +479,7 @@ const runPagedQuery = function( if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback(null, [], null, false); } @@ -658,6 +666,7 @@ const _iterateAll = function( if (err) { return callback(err); } + if (_.isEmpty(rows)) { // Notify the caller that we've finished return callback(); @@ -688,6 +697,7 @@ const _iterateAll = function( row[eachNewRowContent.key] = eachNewRowContent.value; }); } + requestedRowColumns.push(row); }); rows = requestedRowColumns; @@ -737,6 +747,7 @@ const _buildIterateAllQuery = function(columnNames, columnFamily, keyColumnName, // Return as-is } + return columnName; }).join(', '); } @@ -794,6 +805,7 @@ const constructUpsertCQL = function(cf, rowKey, rowValue, values, ttl) { if (!cf || !rowKey || !rowValue || !_.isObject(values) || _.isEmpty(values)) { return false; } + if (_.isArray(rowKey)) { // If the row key is an array, the row value should be an array of the same length if (!_.isArray(rowValue) || rowKey.length !== rowValue.length) { diff --git a/packages/oae-util/lib/redis.js b/packages/oae-util/lib/redis.js index 4c30da4425..7648c8f0ee 100644 --- a/packages/oae-util/lib/redis.js +++ b/packages/oae-util/lib/redis.js @@ -31,6 +31,7 @@ const init = function(redisConfig, callback) { if (err) { return callback(err); } + client = _client; return callback(); }); @@ -44,9 +45,11 @@ const _selectIndex = function(client, _config, callback) { log().error({ err }, "Couldn't select the redis DB index '%s'", dbIndex); return callback(err); } + return callback(null, client); }); }; + /** * Creates a redis connection from a defined set of configuration. * @@ -76,6 +79,7 @@ const createClient = function(_config, callback) { if (isDown) { log().error('Reconnected to redis \\o/'); } + isDown = false; }); @@ -92,9 +96,11 @@ const _authenticateRedis = (client, _config, callback) => { log().error({ err }, "Couldn't authenticate with redis."); return callback(err); } + _selectIndex(client, _config, callback); }); } + _selectIndex(client, _config, callback); }; @@ -116,7 +122,8 @@ const flush = function(callback) { if (err) { return callback({ code: 500, msg: err }); } - return callback(); + + callback(); }); }; diff --git a/packages/oae-util/lib/server.js b/packages/oae-util/lib/server.js index a6ecf11231..ba45bc2916 100644 --- a/packages/oae-util/lib/server.js +++ b/packages/oae-util/lib/server.js @@ -59,16 +59,16 @@ const setupServer = function(port, _config) { _applyAvailabilityHandling(app.httpServer, app, port); /*! - * We support the following type of request encodings: - * - * * urlencoded (regular POST requests) - * * application/json - * * multipart (file uploads) - * - * A maximum limit of 250kb is imposed for `urlencoded` and `application/json` requests. This limit only - * applies to the *incoming request data*. If the client needs to send more than 250kb, it should consider - * using a proper multipart form request. - */ + * We support the following type of request encodings: + * + * * urlencoded (regular POST requests) + * * application/json + * * multipart (file uploads) + * + * A maximum limit of 250kb is imposed for `urlencoded` and `application/json` requests. This limit only + * applies to the *incoming request data*. If the client needs to send more than 250kb, it should consider + * using a proper multipart form request. + */ app.use(bodyParser.urlencoded({ limit: '250kb', extended: true })); app.use(bodyParser.json({ limit: '250kb' })); app.use(multipart(config.files)); @@ -122,15 +122,10 @@ const setupRouter = function(app) { ) ); } else if (!isRouteValid) { - throw new Error( - util.format('Invalid route path "%s" while binding route to OAE Router', route.toString()) - ); + throw new Error(util.format('Invalid route path "%s" while binding route to OAE Router', route.toString())); } else if (!isHandlerValid) { throw new Error( - util.format( - 'Invalid method handler given for route "%s" while binding to OAE Router', - route.toString() - ) + util.format('Invalid method handler given for route "%s" while binding to OAE Router', route.toString()) ); } @@ -184,23 +179,23 @@ const addSafePathPrefix = function(pathPrefix) { */ const postInitializeServer = function(app, router) { /*! - * Referer-based CSRF protection. If the request is not safe (e.g., POST, DELETE) and the origin of the request (as - * specified by the HTTP Referer header) does not match the target host of the request (as specified by the HTTP - * Host header), then the request will result in a 500 error. - * - * While referer-based protection is not highly recommended due to spoofing possibilities in insecure environments, - * it currently offers the best trade-off between ease of use (e.g., for cURL interoperability), effort and security - * against CSRF attacks. - * - * Middleware that gets called earlier, can force the CSRF check to be skipped by setting `_checkCSRF` on the request. - * - * If using a utility such as `curl` to POST requests to the API, you can bypass this by just setting the referer - * header to "/": - * - * curl -X POST -e / http://my.oae.com/api/auth/login - * - * More information about CSRF attacks: http://en.wikipedia.org/wiki/Cross-site_request_forgery - */ + * Referer-based CSRF protection. If the request is not safe (e.g., POST, DELETE) and the origin of the request (as + * specified by the HTTP Referer header) does not match the target host of the request (as specified by the HTTP + * Host header), then the request will result in a 500 error. + * + * While referer-based protection is not highly recommended due to spoofing possibilities in insecure environments, + * it currently offers the best trade-off between ease of use (e.g., for cURL interoperability), effort and security + * against CSRF attacks. + * + * Middleware that gets called earlier, can force the CSRF check to be skipped by setting `_checkCSRF` on the request. + * + * If using a utility such as `curl` to POST requests to the API, you can bypass this by just setting the referer + * header to "/": + * + * curl -X POST -e / http://my.oae.com/api/auth/login + * + * More information about CSRF attacks: http://en.wikipedia.org/wiki/Cross-site_request_forgery + */ app.use((req, res, next) => { // If earlier middleware determined that CSRF is not required, we can skip the check if (req._checkCSRF === false) { @@ -217,11 +212,7 @@ const postInitializeServer = function(app, router) { }, 'CSRF validation failed: attempted to execute unsafe operation from untrusted origin' ); - return _abort( - res, - 500, - 'CSRF validation failed: attempted to execute unsafe method from untrusted origin' - ); + return _abort(res, 500, 'CSRF validation failed: attempted to execute unsafe method from untrusted origin'); } return next(); @@ -361,8 +352,10 @@ const _isSameOrigin = function(req) { // we deem it not to be the same origin. return false; } + return true; } + // If the referer is a relative uri, it must be from same origin. return true; }; diff --git a/packages/oae-version/lib/api.js b/packages/oae-version/lib/api.js index c97547dd5f..a59d4d5208 100644 --- a/packages/oae-version/lib/api.js +++ b/packages/oae-version/lib/api.js @@ -13,18 +13,19 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); const fs = require('fs'); -const gift = require('gift'); const path = require('path'); const util = require('util'); +const gift = require('gift'); +const _ = require('underscore'); const IO = require('oae-util/lib/io'); const log = require('oae-logger').logger('oae-version'); // A variable that will hold the path to the UI directory -let hilaryDirectory = path.resolve(__dirname, '..', '..', '..'); +const hilaryDirectory = path.resolve(__dirname, '..', '..', '..'); let _uiPath = null; +const FRONTEND_REPO = '3akai-ux'; // A variable that will hold a copy of the version information let _version = null; @@ -35,7 +36,7 @@ let _version = null; * @param {String} uiPath The path to the UI directory */ const init = function(uiPath) { - _uiPath = uiPath; + _uiPath = uiPath; }; /** @@ -48,27 +49,27 @@ const init = function(uiPath) { * @param {String} callback.version.3akai-ux The version information for the UI */ const getVersion = function(callback) { - if (_version) { - return callback(null, _version); - } + if (_version) { + return callback(null, _version); + } - _getHilaryVersion(function(err, hilaryVersion) { - if (err) { - return callback(err); - } + _getHilaryVersion(function(err, hilaryVersion) { + if (err) { + return callback(err); + } - _getUIVersion(function(err, uiVersion) { - if (err) { - return callback(err); - } + _getUIVersion(function(err, uiVersion) { + if (err) { + return callback(err); + } - _version = { - 'hilary': hilaryVersion, - '3akai-ux': uiVersion - }; - return callback(null, _version); - }); + _version = { + hilary: hilaryVersion, + FRONTEND_REPO: uiVersion + }; + return callback(null, _version); }); + }); }; /** @@ -80,7 +81,7 @@ const getVersion = function(callback) { * @api private */ function _getHilaryVersion(callback) { - _getVersionInfo(hilaryDirectory, callback); + _getVersionInfo(hilaryDirectory, callback); } /** @@ -92,36 +93,31 @@ function _getHilaryVersion(callback) { * @api private */ function _getUIVersion(callback) { + gift.init(hilaryDirectory, (err, repo) => { + repo.tree().contents((err, children) => { + // Find the 3akai-ux submodule within the tree + const submodulePointer = _.find(children, eachChild => { + return eachChild.name === FRONTEND_REPO; + }).id; + + // Now let's check if the submodule pointer corresponds to the last commit on 3akai-ux + // if it does, that means everything has been checked out properly + gift.init(path.resolve(hilaryDirectory, _uiPath), (err, submodule) => { + submodule.current_commit((err, lastCheckedOutCommit) => { + if (submodulePointer === lastCheckedOutCommit.id) { + const version = { + branch: 'HEAD detached at ' + lastCheckedOutCommit.id, + date: lastCheckedOutCommit.committed_date, + type: 'git submodule' + }; + return callback(null, version); + } - let submodulePointer = ""; - let lastCheckedOutCommit = ""; - - gift.init(hilaryDirectory, (err, repo) => { - repo.tree().contents((err, children) => { - - // find the 3akai-ux submodule within the tree - const submodulePointer = _.find(children, (eachChild) => { - return eachChild.name === '3akai-ux'; - }).id; - - // now let's check if the submodule pointer corresponds to the last commit on 3akai-ux - // if it does, that means everything has been checked out properly - gift.init(path.resolve(hilaryDirectory, _uiPath), (err, submodule) => { - submodule.current_commit((err, lastCheckedOutCommit) => { - if (submodulePointer === lastCheckedOutCommit.id) { - let version = { - branch: 'HEAD detached at ' + lastCheckedOutCommit.id, - date: lastCheckedOutCommit.committed_date, - type: 'git submodule' - }; - return callback(null, version); - } else { - return callback({'code': 500, 'msg': 'The submodule hasn\'t been checked out properly'}); - } - }); - }); + return callback({ code: 500, msg: "The submodule hasn't been checked out properly" }); }); + }); }); + }); } /** @@ -137,15 +133,17 @@ function _getUIVersion(callback) { * @api private */ function _getVersionInfo(directory, callback) { - _getBuildInfoFile(directory, function(err, buildInfoPath) { - if (err) { - return callback(err); - } else if (buildInfoPath) { - return _getBuildVersion(buildInfoPath, callback); - } else { - return _getGitVersion(directory, callback); - } - }); + _getBuildInfoFile(directory, function(err, buildInfoPath) { + if (err) { + return callback(err); + } + + if (buildInfoPath) { + return _getBuildVersion(buildInfoPath, callback); + } + + return _getGitVersion(directory, callback); + }); } /** @@ -159,24 +157,28 @@ function _getVersionInfo(directory, callback) { * @api private */ function _getBuildVersion(buildInfoPath, callback) { - fs.readFile(buildInfoPath, function(err, buildInfo) { - if (err) { - return callback({'code': 500, 'msg': 'Unable to read the build info file'}); - } + fs.readFile(buildInfoPath, function(err, buildInfo) { + if (err) { + return callback({ code: 500, msg: 'Unable to read the build info file' }); + } - try { - buildInfo = JSON.parse(buildInfo); - } catch (ex) { - log().error({ - 'err': ex, - 'path': buildInfoPath - }, 'Unable to parse the build info file'); - return callback({'code': 500, 'msg': 'Unable to parse the build info file'}); - } - buildInfo.type = 'archive'; + try { + buildInfo = JSON.parse(buildInfo); + } catch (error) { + log().error( + { + err: error, + path: buildInfoPath + }, + 'Unable to parse the build info file' + ); + return callback({ code: 500, msg: 'Unable to parse the build info file' }); + } - return callback(null, buildInfo); - }); + buildInfo.type = 'archive'; + + return callback(null, buildInfo); + }); } /** @@ -189,60 +191,72 @@ function _getBuildVersion(buildInfoPath, callback) { * @api private */ function _getGitVersion(directory, callback) { - gift.init(directory, function(err, repo) { + gift.init(directory, function(err, repo) { + if (err) { + log().error( + { + err, + path: directory + }, + 'Could not open the git repo to get the version information' + ); + return callback({ code: 500, msg: 'Could not open the git repo' }); + } + + // Get the tag info + repo.tags(function(err, tags) { + if (err) { + log().error( + { + err, + path: directory + }, + 'Could not get the git tags' + ); + return callback({ code: 500, msg: 'Could not get the git tags' }); + } + + const tagsByCommitId = _.indexBy(tags, function(tag) { + return tag.commit.id; + }); + + // Get the current branch info + repo.branch(function(err, branch) { if (err) { - log().error({ - 'err': err, - 'path': directory - }, 'Could not open the git repo to get the version information'); - return callback({'code': 500, 'msg': 'Could not open the git repo'}); + log().error( + { + err, + path: directory + }, + 'Could not get the current git branch for the version information' + ); + return callback({ code: 500, msg: 'Could not get the branch information' }); } - // Get the tag info - repo.tags(function(err, tags) { - if (err) { - log().error({ - 'err': err, - 'path': directory - }, 'Could not get the git tags'); - return callback({'code': 500, 'msg': 'Could not get the git tags'}); - } - - let tagsByCommitId = _.indexBy(tags, function(tag) { - return tag.commit.id; - }); - - // Get the current branch info - repo.branch(function (err, branch) { - if (err) { - log().error({ - 'err': err, - 'path': directory - }, 'Could not get the current git branch for the version information'); - return callback({ 'code': 500, 'msg': 'Could not get the branch information' }); - } - - // Get the number of commits since the last tag - _getGitDescribeVersion(repo, branch.name, tagsByCommitId, function (err, describeVersion) { - if (err) { - log().error({ - 'err': err, - 'path': directory - }, 'Could not get the git describe version for a repo'); - return callback({ 'code': 500, 'msg': 'Could not get the branch information' }); - } - - let buildInfo = { - 'branch': branch.name, - 'date': branch.commit.committed_date, - 'type': 'git', - 'version': describeVersion - }; - return callback(null, buildInfo); - }); - }); + // Get the number of commits since the last tag + _getGitDescribeVersion(repo, branch.name, tagsByCommitId, function(err, describeVersion) { + if (err) { + log().error( + { + err, + path: directory + }, + 'Could not get the git describe version for a repo' + ); + return callback({ code: 500, msg: 'Could not get the branch information' }); + } + + const buildInfo = { + branch: branch.name, + date: branch.commit.committed_date, + type: 'git', + version: describeVersion + }; + return callback(null, buildInfo); }); + }); }); + }); } /** @@ -260,35 +274,36 @@ function _getGitVersion(directory, callback) { * @api private */ function _getGitDescribeVersion(repo, branchName, tagsByCommitId, callback, _nrOfCommits, _lastCommitSha) { - _nrOfCommits = _nrOfCommits || 0; - repo.commits(branchName, 30, _nrOfCommits, function(err, commits) { - if (err) { - return callback(err); - } + _nrOfCommits = _nrOfCommits || 0; + repo.commits(branchName, 30, _nrOfCommits, function(err, commits) { + if (err) { + return callback(err); + } - // Retain the sha1 of the last commit on this branch - _lastCommitSha = _lastCommitSha || commits[0].id; - - // Try to find the last tagged commit in these set of commits - let lastTag = null; - for (let i = 0; i < commits.length; i++) { - if (tagsByCommitId[commits[i].id]) { - lastTag = tagsByCommitId[commits[i].id].name; - break; - } - _nrOfCommits++; - } + // Retain the sha1 of the last commit on this branch + _lastCommitSha = _lastCommitSha || commits[0].id; - // If we found a tag in the set of commits we can return the git describe information - if (lastTag) { - let describeVersion = util.format('%s+%d+%s', lastTag, _nrOfCommits, _lastCommitSha); - return callback(null, describeVersion); + // Try to find the last tagged commit in these set of commits + let lastTag = null; + for (let i = 0; i < commits.length; i++) { + if (tagsByCommitId[commits[i].id]) { + lastTag = tagsByCommitId[commits[i].id].name; + break; + } - // Otherwise we need to retrieve the next set of commits - } else { - return _getGitDescribeVersion(repo, branchName, tagsByCommitId, callback, _nrOfCommits, _lastCommitSha); - } - }); + _nrOfCommits++; + } + + // If we found a tag in the set of commits we can return the git describe information + if (lastTag) { + const describeVersion = util.format('%s+%d+%s', lastTag, _nrOfCommits, _lastCommitSha); + return callback(null, describeVersion); + + // Otherwise we need to retrieve the next set of commits + } + + return _getGitDescribeVersion(repo, branchName, tagsByCommitId, callback, _nrOfCommits, _lastCommitSha); + }); } /** @@ -302,25 +317,30 @@ function _getGitDescribeVersion(repo, branchName, tagsByCommitId, callback, _nrO * @api private */ function _getBuildInfoFile(directory, callback) { - let buildInfoPath = _getBuildInfoFilePath(directory); - IO.exists(buildInfoPath, function(err, exists) { - if (err) { - log.error({ - 'directory': directory, - 'err': err - }, 'Unable to check if a build info file exists'); - return callback(err); - } else if (exists) { - return callback(null, buildInfoPath); - } else { - let parent = path.dirname(directory); - if (parent !== directory) { - return _getBuildInfoFile(parent, callback); - } else { - return callback(); - } - } - }); + const buildInfoPath = _getBuildInfoFilePath(directory); + IO.exists(buildInfoPath, function(err, exists) { + if (err) { + log.error( + { + directory, + err + }, + 'Unable to check if a build info file exists' + ); + return callback(err); + } + + if (exists) { + return callback(null, buildInfoPath); + } + + const parent = path.dirname(directory); + if (parent !== directory) { + return _getBuildInfoFile(parent, callback); + } + + return callback(); + }); } /** @@ -331,9 +351,10 @@ function _getBuildInfoFile(directory, callback) { * @api private */ function _getBuildInfoFilePath(directory) { - return path.join(directory, 'build-info.json'); + return path.join(directory, 'build-info.json'); } module.exports = { - init, getVersion + init, + getVersion }; diff --git a/packages/oae-version/tests/test-version.js b/packages/oae-version/tests/test-version.js index e2e4566768..7d4ceaee6d 100644 --- a/packages/oae-version/tests/test-version.js +++ b/packages/oae-version/tests/test-version.js @@ -13,67 +13,59 @@ * permissions and limitations under the License. */ -var _ = require('underscore'); -var assert = require('assert'); -var fs = require('fs'); -var util = require('util'); +const assert = require('assert'); +const _ = require('underscore'); -var RestAPI = require('oae-rest'); -var RestContext = require('oae-rest/lib/model').RestContext; -var TenantsTestUtil = require('oae-tenants/lib/test/util'); -var TestsUtil = require('oae-tests'); +const RestAPI = require('oae-rest'); +const TestsUtil = require('oae-tests'); -var UIAPI = require('oae-ui'); -var UIConstants = require('oae-ui/lib/constants').UIConstants; -var UITestUtil = require('oae-ui/lib/test/util'); +const FRONTEND_REPO = '3akai-ux'; describe('Version information', function() { + /** + * Test that verifies that the version information is returned + */ + it('should return the version information', function(callback) { + // Create various rest contexts + const anonTenantRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); + const adminTenantRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.cam.host); + const anonGlobalRestContext = TestsUtil.createGlobalRestContext(); + const globalAdminRestContext = TestsUtil.createGlobalAdminRestContext(); + TestsUtil.generateTestUsers(adminTenantRestContext, 1, function(err, users, user) { + assert.ok(!err); + const userTenantRestContext = user.restContext; - /** - * Test that verifies that the version information is returned - */ - it('should return the version information', function(callback) { - // Create various rest contexts - var anonTenantRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); - var adminTenantRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.cam.host); - var anonGlobalRestContext = TestsUtil.createGlobalRestContext(); - var globalAdminRestContext = TestsUtil.createGlobalAdminRestContext(); - TestsUtil.generateTestUsers(adminTenantRestContext, 1, function(err, users, user) { - assert.ok(!err); - var userTenantRestContext = user.restContext; - - // Verify the version information on regular tenancies - _verifyVersionInformation(anonTenantRestContext, function() { - _verifyVersionInformation(userTenantRestContext, function() { - _verifyVersionInformation(adminTenantRestContext, function() { - - // Verify the version information on the global admin - _verifyVersionInformation(anonGlobalRestContext, function() { - _verifyVersionInformation(globalAdminRestContext, callback); - }); - }); - }); + // Verify the version information on regular tenancies + _verifyVersionInformation(anonTenantRestContext, function() { + _verifyVersionInformation(userTenantRestContext, function() { + _verifyVersionInformation(adminTenantRestContext, function() { + // Verify the version information on the global admin + _verifyVersionInformation(anonGlobalRestContext, function() { + _verifyVersionInformation(globalAdminRestContext, callback); }); + }); }); + }); }); + }); - /*! - * Verify the version information - * - * @param {RestContext} restContext The rest context to get the version information with - * @param {Function} callback Standard callback function - * @throws {AssertionError} Thrown if any assertions fail - */ - function _verifyVersionInformation(restContext, callback) { - RestAPI.Version.getVersion(restContext, function(err, version) { - assert.ok(!err); - assert.ok(_.isObject(version)); - assert.strictEqual(_.size(version), 2); - assert.ok(_.isObject(version.hilary)); - assert.strictEqual(_.size(version.hilary), 4); - assert.ok(_.isObject(version['3akai-ux'])); - assert.strictEqual(_.size(version['3akai-ux']), 3); - return callback(); - }); - } + /*! + * Verify the version information + * + * @param {RestContext} restContext The rest context to get the version information with + * @param {Function} callback Standard callback function + * @throws {AssertionError} Thrown if any assertions fail + */ + function _verifyVersionInformation(restContext, callback) { + RestAPI.Version.getVersion(restContext, function(err, version) { + assert.ok(!err); + assert.ok(_.isObject(version)); + assert.strictEqual(_.size(version), 2); + assert.ok(_.isObject(version.hilary)); + assert.strictEqual(_.size(version.hilary), 4); + assert.ok(_.isObject(version[FRONTEND_REPO])); + assert.strictEqual(_.size(version[FRONTEND_REPO]), 3); + callback(); + }); + } }); From 35eeda1320997e033f2c5453f47d1be1ffdbe2c6 Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Wed, 27 Mar 2019 15:55:38 +0000 Subject: [PATCH 02/21] docs: fix a couple JSDoc declarations --- packages/oae-content/lib/api.js | 2 +- packages/oae-content/tests/test-content.js | 3 ++- packages/oae-lti/lib/rest.js | 20 ++++++------------- .../lib/processors/collabdoc/collabdoc.js | 3 ++- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/packages/oae-content/lib/api.js b/packages/oae-content/lib/api.js index 035b8d9e53..cb801e63b4 100644 --- a/packages/oae-content/lib/api.js +++ b/packages/oae-content/lib/api.js @@ -630,7 +630,7 @@ const createCollabSheet = function(ctx, displayName, description, visibility, ad * @param {String} [description] A longer description for the content item * @param {String} visibility The visibility of the collaborative document. One of `public`, `loggedin`, `private` * @param {Object} roles Object where the keys represent principal ids that need to be added to the content upon creation and the values represent the role that principal will have. Possible values are "viewer" and "manager", as well as "editor" for collabdocs and collabsheets - * @param {String} folders The ids of the folders to which this content item should be added + * @param {String} folderIds The ids of the folders to which this content item should be added * @param {Object} otherValues JSON object where the keys represent other metadata values that need to be stored, and the values represent the metadata values * @param {Object} revisionData JSON object where the keys represent revision columns that need to be stored, and the values represent the revision values * @param {Function} callback Standard callback function diff --git a/packages/oae-content/tests/test-content.js b/packages/oae-content/tests/test-content.js index 3dfe0c20fc..8ab15fea5e 100644 --- a/packages/oae-content/tests/test-content.js +++ b/packages/oae-content/tests/test-content.js @@ -209,7 +209,7 @@ describe('Content', () => { * whether or not it can be seen in the library of the creator * @param {RestContext} restCtx Standard REST Context object that contains the current tenant URL and the current * user credentials - * @param {String} creator The user id for which we want to check the library + * @param {String} libraryToCheck The user id (creator) for which we want to check the library * @param {Content} contentObj The content object we'll be running checks for * @param {Boolean} expectAccess Whether or not we expect the current user to have access to the piece of content * @param {Boolean} expectManager Whether or not we expect the current user to be able to manage the piece of content @@ -556,6 +556,7 @@ describe('Content', () => { new Context(tenant, contexts.branden.user), 'local:2012/12/06/file.doc' ); + // eslint-disable-next-line node/no-deprecated-api const parsedUrl = url.parse(signedDownloadUrl, true); // Branden should be able to download it because he is super awesome and important (In this case, downloading = 204) diff --git a/packages/oae-lti/lib/rest.js b/packages/oae-lti/lib/rest.js index 53387a66bf..644a2b4aef 100644 --- a/packages/oae-lti/lib/rest.js +++ b/packages/oae-lti/lib/rest.js @@ -70,21 +70,13 @@ OAE.tenantRouter.on('post', '/api/lti/:groupId/create', (req, res) => { displayName: req.body.displayName, description: req.body.description }; - LtiApi.addLtiTool( - req.ctx, - req.params.groupId, - req.body.url, - req.body.secret, - req.body.key, - opts, - (err, tool) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(201).send(tool); + LtiApi.addLtiTool(req.ctx, req.params.groupId, req.body.url, req.body.secret, req.body.key, opts, (err, tool) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + return res.status(201).send(tool); + }); }); /** diff --git a/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js b/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js index 5c047d83e6..0e97586c1d 100644 --- a/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js +++ b/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js @@ -162,7 +162,8 @@ const generatePreviews = function(ctx, contentObj, callback) { * Take the provided Collab HTML, wrap into the preview template and store it to disk * * @param {Context} ctx Standard context object containing the current user and the current tenant - * @param {String} collabHtml The HTML to wrap into the preview template + * @param {String} collabHtml The HTML to wrap into the preview template + * @param {String} type Standard callback function * @param {Function} callback Standard callback function * @param {Object} callback.err An error that occurred, if any * @param {String} callback.path The path the file has been written to From 0ba4542fc3333dc334e587d45ad374357b400a0b Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Thu, 28 Mar 2019 09:21:57 +0000 Subject: [PATCH 03/21] refactor: ES6 modules through ESM --- app.js | 23 ++-- config.js | 14 ++ ...igration_runner.js => migration-runner.js} | 28 ++-- index.js | 4 + migrate.js | 14 +- package-lock.json | 7 +- package.json | 9 +- packages/oae-activity/lib/internal/emitter.js | 2 +- packages/oae-content/lib/api.js | 44 +++---- packages/oae-content/lib/backends/util.js | 29 ++++- packages/oae-content/lib/rest.js | 18 ++- packages/oae-doc/lib/api.js | 29 +++-- packages/oae-doc/lib/init.js | 2 +- packages/oae-doc/lib/rest.js | 47 ++++--- packages/oae-logger/lib/api.js | 22 ++-- packages/oae-logger/lib/init.js | 4 +- packages/oae-logger/tests/test-logger.js | 121 +++++++++--------- .../lib/processors/file/pdf.js | 27 ++-- packages/oae-tests/lib/util.js | 2 +- .../{beforeTests.js => before-tests.js} | 10 +- packages/oae-tests/runner/init.js | 4 + packages/oae-util/lib/modules.js | 5 + packages/oae-util/lib/oae.js | 14 +- packages/oae-util/lib/server.js | 4 +- process.json | 3 +- 25 files changed, 270 insertions(+), 216 deletions(-) rename etc/migration/{migration_runner.js => migration-runner.js} (78%) create mode 100644 index.js rename packages/oae-tests/runner/{beforeTests.js => before-tests.js} (94%) create mode 100644 packages/oae-tests/runner/init.js diff --git a/app.js b/app.js index 2324d8a7b7..c21f842a7a 100755 --- a/app.js +++ b/app.js @@ -15,31 +15,29 @@ * permissions and limitations under the License. */ -/* eslint-disable security/detect-non-literal-require */ -const path = require('path'); -const repl = require('repl'); -const PrettyStream = require('bunyan-prettystream'); -const optimist = require('optimist'); -const _ = require('underscore'); +import path from 'path'; +import repl from 'repl'; +import PrettyStream from 'bunyan-prettystream'; +import optimist from 'optimist'; +import _ from 'underscore'; + +import * as OAE from 'oae-util/lib/oae'; +import { logger } from 'oae-logger'; + +const log = logger(); const { argv } = optimist .usage('$0 [--config ]') .alias('c', 'config') .describe('c', 'Specify an alternate config file') .default('c', path.join(__dirname, '/config.js')) - .alias('h', 'help') .describe('h', 'Show usage information') - .alias('i', 'interactive') .describe('i', 'Start an interactive shell, implies --pretty') - .alias('p', 'pretty') .describe('p', 'Pretty print the logs'); -const OAE = require('oae-util/lib/oae'); -const log = require('oae-logger').logger(); - if (argv.help) { optimist.showHelp(); process.exit(0); @@ -57,7 +55,6 @@ if (argv.config.match(/^\.\//)) { const configPath = argv.config; let { config } = require(configPath); - const envConfigPath = `${process.cwd()}/${process.env.NODE_ENV || 'local'}`; const envConfig = require(envConfigPath).config; config = _.extend({}, config, envConfig); diff --git a/config.js b/config.js index 5dd593252f..bdd18b0107 100644 --- a/config.js +++ b/config.js @@ -16,6 +16,7 @@ /* eslint-disable camelcase, capitalized-comments */ const Path = require('path'); const bunyan = require('bunyan'); + const config = {}; const LOCALHOST = 'localhost'; module.exports.config = config; @@ -431,6 +432,19 @@ config.etherpad = { ] }; +/** + * `config.ethercalc` + * + * Configuration namespace for the ethercalc logic. + * + * @param {String} host The hostname or IP address on which Hilary will be accessing the Ethercalc API. + * @param {Number} port The port number on which Hilary will be accessing the ethercalc API. + */ +config.ethercalc = { + host: LOCALHOST, + port: 8000, + protocol: 'http' +}; /** * `config.tincanapi` * diff --git a/etc/migration/migration_runner.js b/etc/migration/migration-runner.js similarity index 78% rename from etc/migration/migration_runner.js rename to etc/migration/migration-runner.js index 9d10a07135..49cf841c10 100644 --- a/etc/migration/migration_runner.js +++ b/etc/migration/migration-runner.js @@ -13,12 +13,23 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const path = require('path'); -const { promisify } = require('util'); -const async = require('async'); +import fs from 'fs'; +import path from 'path'; +import { promisify } from 'util'; +import { logger, refreshLogConfiguration } from 'oae-logger'; +import PrettyStream from 'bunyan-prettystream'; +import { eachSeries } from 'async'; +import { config } from '../../config'; -const log = require('oae-logger').logger(); +const _createLogger = function(config) { + const prettyLog = new PrettyStream(); + prettyLog.pipe(process.stdout); + config.log.streams[0].stream = prettyLog; + refreshLogConfiguration(config.log); + return logger(); +}; + +const log = _createLogger(config); const readFolderContents = promisify(fs.readdir); const checkIfExists = promisify(fs.stat); @@ -49,7 +60,8 @@ const lookForMigrations = async function(allModules) { return migrationsToRun; }; -const runMigrations = async function(dbConfig, callback) { +export const runMigrations = async function(dbConfig, callback) { + log().info('Running migrations for keyspace ' + dbConfig.keyspace + '...'); const data = {}; try { @@ -63,7 +75,7 @@ const runMigrations = async function(dbConfig, callback) { }) .then(() => { require(path.join(PACKAGES_FOLDER, 'oae-util', LIB_FOLDER, 'cassandra.js')).init(dbConfig, () => { - async.eachSeries( + eachSeries( data.allMigrationsToRun, (eachMigration, done) => { log().info(`Updating schema for ${eachMigration.name}`); @@ -86,5 +98,3 @@ const runMigrations = async function(dbConfig, callback) { callback(error); } }; - -module.exports = { runMigrations }; diff --git a/index.js b/index.js new file mode 100644 index 0000000000..eb59e9c346 --- /dev/null +++ b/index.js @@ -0,0 +1,4 @@ +// eslint-disable-next-line no-global-assign +require = require('esm')(module /* , options */); +// Import the rest of our application. +module.exports = require('./app.js'); diff --git a/migrate.js b/migrate.js index 04e5e16101..542f343912 100644 --- a/migrate.js +++ b/migrate.js @@ -14,14 +14,12 @@ * permissions and limitations under the License. */ -const path = require('path'); -const optimist = require('optimist'); +import optimist from 'optimist'; -const log = require('oae-logger').logger(); +import { runMigrations } from './etc/migration/migration-runner'; +import { config } from './config'; -const config = require(path.join(__dirname, 'config.js')); -const dbConfig = config.config.cassandra; -const migrationRunner = require(path.join(__dirname, 'etc/migration/migration_runner.js')); +const dbConfig = config.cassandra; const { argv } = optimist .usage('$0 [--keyspace ]') @@ -37,9 +35,7 @@ if (argv.help) { dbConfig.keyspace = argv.keyspace === true ? dbConfig.keyspace : argv.keyspace; const execute = function() { - log().info('Running migrations for keyspace ' + dbConfig.keyspace + '...'); - migrationRunner.runMigrations(dbConfig, () => { - log().info('All done.'); + runMigrations(dbConfig, () => { process.exit(0); }); }; diff --git a/package-lock.json b/package-lock.json index bede20c22c..a4a2035e16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3226,10 +3226,9 @@ "dev": true }, "esm": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.11.tgz", - "integrity": "sha512-OhgzK4tmov6Ih2gQ28k8e5kV07sGgEKG+ys3PqbDd2FBXpsZkGpFotFbrm0+KmuD2ktaV4hdPYQTDMpq9FjeTA==", - "dev": true + "version": "3.2.20", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.20.tgz", + "integrity": "sha512-NA92qDA8C/qGX/xMinDGa3+cSPs4wQoFxskRrSnDo/9UloifhONFm4sl4G+JsyCqM007z2K+BfQlH5rMta4K1Q==" }, "espree": { "version": "5.0.1", diff --git a/package.json b/package.json index cf70413bd3..4a1886d21d 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "dox": "^0.9.0", "elasticsearchclient": "^0.5.3", "ent": "^2.2.0", + "esm": "^3.2.20", "ethercalc-client": "^0.0.4", "etherpad-lite-client": "^0.8.0", "expand-url": "^0.1.3", @@ -156,10 +157,10 @@ "node": ">=10.13.0" }, "scripts": { - "test": "mocha 'node_modules/oae-tests/runner/beforeTests.js' 'node_modules/oae-*/tests/**/*.js'", - "test-module": "func() { mocha 'node_modules/oae-tests/runner/beforeTests.js' 'node_modules/'$1'/tests/**/*.js'; }; func", - "migrate": "func() { node 'migrate.js' '--keyspace' $1 | npx bunyan; }; func", - "start": "node app.js | npx bunyan", + "test": "mocha -r esm 'node_modules/oae-tests/runner/init.js' 'node_modules/oae-*/tests/**/*.js'", + "test-module": "func() { mocha -r esm 'node_modules/oae-tests/runner/init.js' 'node_modules/'$1'/tests/**/*.js'; }; func", + "migrate": "func() { node -r esm 'migrate.js' '--keyspace' $1 ; }; func", + "start": "node -r esm app.js | npx bunyan", "dev-server": "nodemon app.js | npx bunyan", "snyk-protect": "snyk protect", "lint": "xo --prettier --quiet '**/*.js'", diff --git a/packages/oae-activity/lib/internal/emitter.js b/packages/oae-activity/lib/internal/emitter.js index 8856c88f7d..9441848683 100644 --- a/packages/oae-activity/lib/internal/emitter.js +++ b/packages/oae-activity/lib/internal/emitter.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -var EmitterAPI = require('oae-emitter'); +const EmitterAPI = require('oae-emitter'); // A singleton emitter for activities, so all internal APIs may produce and consume events module.exports = new EmitterAPI.EventEmitter(); diff --git a/packages/oae-content/lib/api.js b/packages/oae-content/lib/api.js index cb801e63b4..d11679a654 100644 --- a/packages/oae-content/lib/api.js +++ b/packages/oae-content/lib/api.js @@ -54,20 +54,20 @@ const COLLABSHEET = 'collabsheet'; * * The `ContentAPI`, as enumerated in `ContentConstants.events`, emits the following events: * - * * `createdComment(ctx, comment, content)`: A new comment was posted for a content item. The `ctx`, `comment` and commented `content` object are provided. - * * `createdContent(ctx, content)`: A new content item was created. The `ctx` and the `content` object that was created are both provided. - * * `deletedComment(ctx, comment, content, deleteType)`: An existing comment has been deleted on a content item. The `ctx`, `content` and target `comment` object are provided. - * * `deletedContent(ctx, contentObj, members)`: A content item was deleted. The 'ctx', the deleted 'contentObj' and the list of authz principals that had this content item in their library - * * `downloadedContent(ctx, content, revision)`: A content item was downloaded. The `ctx`, `content` and the `revision` are all provided. - * * `editedCollabdoc(ctx, contentObj)`: A collaborative document was edited by a user without resulting in a new revision. This happens if the revision-creation was already triggered by another user leaving the document - * * `editedCollabsheet(ctx, contentObj)`: A collaborative spreadsheet was edited by a user without resulting in a new revision. This happens if the revision-creation was already triggered by another user leaving the spreadsheet - * * `getContentLibrary(ctx, principalId, visibility, start, limit, contentObjects)`: A content library was retrieved. - * * `getContentProfile(ctx, content)`: A content profile was retrieved. The `ctx` and the `content` are both provided. - * * `restoredContent(ctx, newContentObj, oldContentObj, restoredRevision)`: An older revision for a content item has been restored. - * * `updatedContent(ctx, newContentObj, oldContentObj)`: A content item was updated. The `ctx`, the updated content object and the content before was updated are provided. - * * `updatedContentBody(ctx, newContentObj, oldContentObj, revision)`: A content item's file body was updated. The `ctx` of the request, the `newContentObj` object after being updated, the `oldContentObj` object before the update, and the revision object. - * * `updatedContentMembers(ctx, content, memberUpdates, addedMemberIds, updatedMemberIds, removedMemberIds)`: A content's members list was updated. The `ctx`, full `content` object of the updated content, and the hash of principalId -> role that outlines the changes that were made are provided, as well as arrays containing the ids of the added members, updated members and removed members that resulted from the change - * * `updatedContentPreview(content)`: A content item's preview has been updated + * `createdComment(ctx, comment, content)`: A new comment was posted for a content item. The `ctx`, `comment` and commented `content` object are provided. + * `createdContent(ctx, content)`: A new content item was created. The `ctx` and the `content` object that was created are both provided. + * `deletedComment(ctx, comment, content, deleteType)`: An existing comment has been deleted on a content item. The `ctx`, `content` and target `comment` object are provided. + * `deletedContent(ctx, contentObj, members)`: A content item was deleted. The 'ctx', the deleted 'contentObj' and the list of authz principals that had this content item in their library + * `downloadedContent(ctx, content, revision)`: A content item was downloaded. The `ctx`, `content` and the `revision` are all provided. + * `editedCollabdoc(ctx, contentObj)`: A collaborative document was edited by a user without resulting in a new revision. This happens if the revision-creation was already triggered by another user leaving the document + * `editedCollabsheet(ctx, contentObj)`: A collaborative spreadsheet was edited by a user without resulting in a new revision. This happens if the revision-creation was already triggered by another user leaving the spreadsheet + * `getContentLibrary(ctx, principalId, visibility, start, limit, contentObjects)`: A content library was retrieved. + * `getContentProfile(ctx, content)`: A content profile was retrieved. The `ctx` and the `content` are both provided. + * `restoredContent(ctx, newContentObj, oldContentObj, restoredRevision)`: An older revision for a content item has been restored. + * `updatedContent(ctx, newContentObj, oldContentObj)`: A content item was updated. The `ctx`, the updated content object and the content before was updated are provided. + * `updatedContentBody(ctx, newContentObj, oldContentObj, revision)`: A content item's file body was updated. The `ctx` of the request, the `newContentObj` object after being updated, the `oldContentObj` object before the update, and the revision object. + * `updatedContentMembers(ctx, content, memberUpdates, addedMemberIds, updatedMemberIds, removedMemberIds)`: A content's members list was updated. The `ctx`, full `content` object of the updated content, and the hash of principalId -> role that outlines the changes that were made are provided, as well as arrays containing the ids of the added members, updated members and removed members that resulted from the change + * `updatedContentPreview(content)`: A content item's preview has been updated */ const emitter = new EmitterAPI.EventEmitter(); @@ -556,15 +556,15 @@ const createCollabDoc = function(ctx, displayName, description, visibility, addi /** * Create a collaborative sheet as a pooled content item - * @param { Context } ctx Standard context object containing the current user and the current tenant + * @param {Context} ctx Standard context object containing the current user and the current tenant * - * @param { String } displayName The display name of the collaborative spreadsheet - * @param { String } [description] A longer description for the collaborative spreadsheet - * @param { String } [visibility] The visibility of the collaborative spreadsheet.One of`public`, `loggedin`, `private` - * @param { Object } [additionalMembers] Object where the keys represent principal ids that need to be added to the content upon creation and the values represent the role that principal will have.Possible values are "viewer", "editor" and "manager" - * @param { String[] } [folders] The ids of the folders to which this collaborative spreadsheet should be added - * @param { Function } callback Standard callback function* @param { Object } callback.err An error that occurred, if any - * @param { Content } callback.content The created collaborative spreadsheet + * @param {String} displayName The display name of the collaborative spreadsheet + * @param {String} [description] A longer description for the collaborative spreadsheet + * @param {String} [visibility] The visibility of the collaborative spreadsheet.One of`public`, `loggedin`, `private` + * @param {Object} [additionalMembers] Object where the keys represent principal ids that need to be added to the content upon creation and the values represent the role that principal will have.Possible values are "viewer", "editor" and "manager" + * @param {String[]} [folders] The ids of the folders to which this collaborative spreadsheet should be added + * @param {Function} callback Standard callback function* @param { Object } callback.err An error that occurred, if any + * @param {Content} callback.content The created collaborative spreadsheet */ const createCollabSheet = function(ctx, displayName, description, visibility, additionalMembers, folders, callback) { callback = callback || function() {}; diff --git a/packages/oae-content/lib/backends/util.js b/packages/oae-content/lib/backends/util.js index c957ff5472..8aadc31262 100644 --- a/packages/oae-content/lib/backends/util.js +++ b/packages/oae-content/lib/backends/util.js @@ -19,6 +19,10 @@ const ShortId = require('shortid'); const AuthzUtil = require('oae-authz/lib/util'); const VALID_CHARACTERS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'; +const COLLABDOC = 'collabdoc'; +const COLLABSHEET = 'collabsheet'; +const FILE = 'file'; +const LINK = 'link'; /** * Split a content uri into its 2 parts: The storage implementation identifier and the location string. With a @@ -71,6 +75,7 @@ const generateUri = function(file, options) { if (r.resourceId.length < 8) { r.resourceId = _padRight(r.resourceId, 8); } + hash = _hash(r.resourceType, r.tenantAlias, r.resourceId); // In all other cases we generate a random hash. @@ -86,6 +91,7 @@ const generateUri = function(file, options) { if (options.prefix) { uri += '/' + options.prefix; } + uri += '/' + filename; // Because the URI gets used in file paths sometimes @@ -127,10 +133,31 @@ const _padRight = function(str, minLength) { while (str.length < minLength) { str += VALID_CHARACTERS[Math.floor(Math.random() * VALID_CHARACTERS.length)]; } + return str; }; +const isResourceACollabSheet = function(resourceType) { + return resourceType === COLLABSHEET; +}; + +const isResourceACollabDoc = function(resourceType) { + return resourceType === COLLABDOC; +}; + +const isResourceALink = function(resourceType) { + return resourceType === LINK; +}; + +const isResourceAFile = function(resourceType) { + return resourceType === FILE; +}; + module.exports = { splitUri, - generateUri + generateUri, + isResourceACollabDoc, + isResourceACollabSheet, + isResourceAFile, + isResourceALink }; diff --git a/packages/oae-content/lib/rest.js b/packages/oae-content/lib/rest.js index a2b2dde4b1..6c08d49311 100644 --- a/packages/oae-content/lib/rest.js +++ b/packages/oae-content/lib/rest.js @@ -13,6 +13,13 @@ * permissions and limitations under the License. */ +import { + isResourceACollabDoc, + isResourceACollabSheet, + isResourceAFile, + isResourceALink +} from 'oae-content/lib/backends/util'; + const querystring = require('querystring'); const _ = require('underscore'); @@ -23,9 +30,6 @@ const OaeUtil = require('oae-util/lib/util'); const ContentAPI = require('./api'); const { ContentConstants } = require('./constants'); -const COLLABDOC = 'collabdoc'; -const COLLABSHEET = 'collabsheet'; - /** * Verify the signature information provided by a signed download request and * pass it on to the download handler to complete the download request @@ -242,7 +246,7 @@ const _createContent = function( callback ) { // Link creation - if (resourceSubType === 'link') { + if (isResourceALink(resourceSubType)) { return ContentAPI.createLink( ctx, displayName, @@ -257,7 +261,7 @@ const _createContent = function( // File creation } - if (resourceSubType === 'file') { + if (isResourceAFile(resourceSubType)) { return ContentAPI.createFile( ctx, displayName, @@ -272,7 +276,7 @@ const _createContent = function( // Collaborative document creation } - if (resourceSubType === COLLABDOC) { + if (isResourceACollabDoc(resourceSubType)) { return ContentAPI.createCollabDoc( ctx, displayName, @@ -287,7 +291,7 @@ const _createContent = function( } // Collaborative spreadsheet creation - if (resourceSubType === COLLABSHEET) { + if (isResourceACollabSheet(resourceSubType)) { return ContentAPI.createCollabSheet( ctx, displayName, diff --git a/packages/oae-doc/lib/api.js b/packages/oae-doc/lib/api.js index 60661baa28..efea4b5a3c 100644 --- a/packages/oae-doc/lib/api.js +++ b/packages/oae-doc/lib/api.js @@ -13,15 +13,18 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const _ = require('underscore'); -const dox = require('dox'); +import fs from 'fs'; +import { logger } from 'oae-logger'; -const IO = require('oae-util/lib/io'); -const Modules = require('oae-util/lib/modules'); -const OaeUtil = require('oae-util/lib/util'); -const { Validator } = require('oae-util/lib/validator'); -const log = require('oae-logger').logger('oae-doc'); +import _ from 'underscore'; +import dox from 'dox'; + +import IO from 'oae-util/lib/io'; +import modules from 'oae-util/lib/modules'; +import OaeUtil from 'oae-util/lib/util'; +import { Validator } from 'oae-util/lib/validator'; + +const log = logger('oae-doc'); // Variable that will be used to cache the back-end and front-end documentation const cachedDocs = { @@ -44,7 +47,7 @@ const initializeDocs = function(uiConfig, callback) { } // Initialize the back-end documentation - _initializeBackendDocs(Modules.getAvailableModules(), callback); + _initializeBackendDocs(modules.getAvailableModules(), callback); }); }; @@ -187,6 +190,7 @@ const _filterFiles = function(fileNames, exclude) { if (fileName.indexOf('.js') !== -1 && _.indexOf(exclude, fileName) === -1) { return true; } + return false; }); }; @@ -237,11 +241,8 @@ const getModuleDocumentation = function(moduleId, type, callback) { if (cachedDocs[type] && cachedDocs[type][moduleId]) { return callback(null, cachedDocs[type][moduleId]); } + return callback({ code: 404, msg: 'No documentation for this module was found' }); }; -module.exports = { - getModules, - initializeDocs, - getModuleDocumentation -}; +export { getModules, initializeDocs, getModuleDocumentation }; diff --git a/packages/oae-doc/lib/init.js b/packages/oae-doc/lib/init.js index 3b2dac610b..c39881d7c2 100644 --- a/packages/oae-doc/lib/init.js +++ b/packages/oae-doc/lib/init.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const DocAPI = require('./api.js'); +import * as DocAPI from './api'; module.exports = function(config, callback) { DocAPI.initializeDocs(config.ui, callback); diff --git a/packages/oae-doc/lib/rest.js b/packages/oae-doc/lib/rest.js index 7b084a23a9..b100c06658 100644 --- a/packages/oae-doc/lib/rest.js +++ b/packages/oae-doc/lib/rest.js @@ -13,11 +13,9 @@ * permissions and limitations under the License. */ -var OAE = require('oae-util/lib/oae'); -var Swagger = require('oae-util/lib/swagger'); - -var DocAPI = require('./api'); - +import * as OAE from 'oae-util/lib/oae'; +import Swagger from 'oae-util/lib/swagger'; +import * as DocAPI from './api'; /** * @REST getDocType @@ -32,13 +30,14 @@ var DocAPI = require('./api'); * @HttpResponse 200 List of documentation modules available * @HttpResponse 400 Invalid or missing module type. Accepted values are "backend" and "frontend" */ -var _getDocModulesByType = function(req, res) { - DocAPI.getModules(req.params.type, function(err, modules) { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(200).send(modules); - }); +const _getDocModulesByType = function(req, res) { + DocAPI.getModules(req.params.type, function(err, modules) { + if (err) { + return res.status(err.code).send(err.msg); + } + + res.status(200).send(modules); + }); }; OAE.tenantRouter.on('get', '/api/doc/:type', _getDocModulesByType); @@ -60,13 +59,14 @@ OAE.globalAdminRouter.on('get', '/api/doc/:type', _getDocModulesByType); * @HttpResponse 400 Missing module id * @HttpResponse 404 No documentation for this module was found */ -var _getDocModule = function(req, res) { - DocAPI.getModuleDocumentation(req.params.module, req.params.type, function(err, docs) { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(200).send(docs); - }); +const _getDocModule = function(req, res) { + DocAPI.getModuleDocumentation(req.params.module, req.params.type, function(err, docs) { + if (err) { + return res.status(err.code).send(err.msg); + } + + res.status(200).send(docs); + }); }; OAE.tenantRouter.on('get', '/api/doc/:type/:module', _getDocModule); @@ -85,10 +85,10 @@ OAE.globalAdminRouter.on('get', '/api/doc/:type/:module', _getDocModule); * @HttpResponse 200 Swagger resource listing available */ OAE.tenantRouter.on('get', '/api/swagger', function(req, res) { - return res.status(200).send(Swagger.getResources(req.ctx)); + return res.status(200).send(Swagger.getResources(req.ctx)); }); OAE.globalAdminRouter.on('get', '/api/swagger', function(req, res) { - return res.status(200).send(Swagger.getResources(req.ctx)); + return res.status(200).send(Swagger.getResources(req.ctx)); }); /** @@ -105,9 +105,8 @@ OAE.globalAdminRouter.on('get', '/api/swagger', function(req, res) { * @HttpResponse 200 Swagger api declaration available */ OAE.tenantRouter.on('get', '/api/swagger/:id', function(req, res) { - return res.status(200).send(Swagger.getApi(req.ctx, req.params.id)); + return res.status(200).send(Swagger.getApi(req.ctx, req.params.id)); }); OAE.globalAdminRouter.on('get', '/api/swagger/:id', function(req, res) { - return res.status(200).send(Swagger.getApi(req.ctx, req.params.id)); + return res.status(200).send(Swagger.getApi(req.ctx, req.params.id)); }); - diff --git a/packages/oae-logger/lib/api.js b/packages/oae-logger/lib/api.js index a4770e9715..f7d3a2faef 100644 --- a/packages/oae-logger/lib/api.js +++ b/packages/oae-logger/lib/api.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); -const bunyan = require('bunyan'); +import util from 'util'; +import _ from 'underscore'; +import bunyan from 'bunyan'; // The logger to use when no logger is specified const SYSTEM_LOGGER_NAME = 'system'; @@ -41,9 +41,7 @@ const refreshLogConfiguration = function(newConfig) { * @param {String} name The name of the logger, this name will be used to identify this logger for potentially custom log configuration * @return {Function} A function that can be used to retrieve the logger takes argument `ctx` */ -const logger = function(name) { - name = name || SYSTEM_LOGGER_NAME; - +const logger = function(name = SYSTEM_LOGGER_NAME) { // Lazy-load the logger and cache it so new loggers don't have to be recreated all the time if (!loggers[name]) { loggers[name] = _createLogger(name); @@ -73,6 +71,7 @@ const _refreshLogConfigurations = function() { * Create a logger with the provided name. * * @param {String} name The name to assign to the created logger + * @return {Object} logger The logging object * @api private */ const _createLogger = function(name) { @@ -118,15 +117,15 @@ const _resolveBootstrapLoggerConfig = function() { /** * Wrap the error logger function so we can count errors with the telemetry api * - * @param {String} name The name of the logger for which the error logger will be wrapped + * @param {String} loggerName The name of the logger for which the error logger will be wrapped * @param {Function} errorFunction The error logger to wrap * @return {Function} A wrapped error logger * @api private */ const _wrapErrorFunction = function(loggerName, errorFunction) { /*! - * Keep track of the error count with the telemetry API before handing control back to Bunyan - */ + * Keep track of the error count with the telemetry API before handing control back to Bunyan + */ const wrapperErrorFunction = function(...args) { // The telemetry API needs to be required inline as there would otherwise be a cyclical dependency const Telemetry = require('oae-telemetry').telemetry('logger'); @@ -144,7 +143,4 @@ const _wrapErrorFunction = function(loggerName, errorFunction) { return wrapperErrorFunction; }; -module.exports = { - refreshLogConfiguration, - logger -}; +export { refreshLogConfiguration, logger }; diff --git a/packages/oae-logger/lib/init.js b/packages/oae-logger/lib/init.js index 89d59095bb..54ba51449d 100644 --- a/packages/oae-logger/lib/init.js +++ b/packages/oae-logger/lib/init.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const LoggerAPI = require('oae-logger'); +import { refreshLogConfiguration } from 'oae-logger'; module.exports = function(config, callback) { - LoggerAPI.refreshLogConfiguration(config.log); + refreshLogConfiguration(config.log); callback(); }; diff --git a/packages/oae-logger/tests/test-logger.js b/packages/oae-logger/tests/test-logger.js index ed27195a68..9deebfbc65 100644 --- a/packages/oae-logger/tests/test-logger.js +++ b/packages/oae-logger/tests/test-logger.js @@ -13,81 +13,80 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const util = require('util'); +import { strict as assert } from 'assert'; -const LoggerAPI = require('oae-logger'); -const RestAPI = require('oae-rest'); -const TelemetryAPI = require('oae-telemetry'); -const TestsUtil = require('oae-tests/lib/util'); +import util from 'util'; +import { logger } from 'oae-logger'; +import * as RestAPI from 'oae-rest'; +import * as TelemetryAPI from 'oae-telemetry'; +import * as TestsUtil from 'oae-tests/lib/util'; describe('Logger', () => { + // Rest context that can be used every time we need to make a request as a global admin + let globalAdminRestContext = null; - // Rest context that can be used every time we need to make a request as a global admin - let globalAdminRestContext = null; + /** + * Function that will fill up the global admin rest context and enable the API + */ + before(callback => { + // Fill up the global admin rest context + globalAdminRestContext = TestsUtil.createGlobalAdminRestContext(); - /** - * Function that will fill up the global admin rest context and enable the API - */ - before((callback) => { - // Fill up the global admin rest context - globalAdminRestContext = TestsUtil.createGlobalAdminRestContext(); - - // Enable the telemetry API - TelemetryAPI.init({'enabled': true}, (err) => { - assert.ok(!err); - return callback(); - }); + // Enable the telemetry API + TelemetryAPI.init({ enabled: true }, err => { + assert.ok(!err); + return callback(); }); + }); - /** - * Function that will disable the telemetry API - */ - after((callback) => { - TelemetryAPI.init({'enabled': false}, (err) => { - assert.ok(!err); - return callback(); - }); + /** + * Function that will disable the telemetry API + */ + after(callback => { + TelemetryAPI.init({ enabled: false }, err => { + assert.ok(!err); + return callback(); }); + }); - /** - * Test that verifies that error counts are counted through the telemetry API - */ - it('verify that error logs are counted through the telemetry API', (callback) => { - // Construct a logger specificly for this test - const loggerName = TestsUtil.generateRandomText(); - const log = LoggerAPI.logger(loggerName); + /** + * Test that verifies that error counts are counted through the telemetry API + */ + it('verify that error logs are counted through the telemetry API', callback => { + // Construct a logger specificly for this test + const loggerName = TestsUtil.generateRandomText(); + const log = logger(loggerName); - // Get the initial count - RestAPI.Telemetry.getTelemetryData(globalAdminRestContext, (err, initialTelemetryData) => { - assert.ok(!err); + // Get the initial count + RestAPI.Telemetry.getTelemetryData(globalAdminRestContext, (err, initialTelemetryData) => { + assert.ok(!err); - // Generate some error logs by using a variation of parameter values - log().error('Simple error log'); - log().error({'err': new Error('error object')}); - log().error({'err': new Error('error object')}, 'With a message'); - log().error({'err': {'foo': 'bar'}}); - log().error({'foo': 'bar'}); - log().error({'foo': 'bar'}, 'With a message'); + // Generate some error logs by using a variation of parameter values + log().error('Simple error log'); + log().error({ err: new Error('error object') }); + log().error({ err: new Error('error object') }, 'With a message'); + log().error({ err: { foo: 'bar' } }); + log().error({ foo: 'bar' }); + log().error({ foo: 'bar' }, 'With a message'); - // Get the new telemetry daya - RestAPI.Telemetry.getTelemetryData(globalAdminRestContext, (err, newTelemetryData) => { - assert.ok(!err); + // Get the new telemetry daya + RestAPI.Telemetry.getTelemetryData(globalAdminRestContext, (err, newTelemetryData) => { + assert.ok(!err); - // Get the initial total error count - let initialTotalCount = 0; - if (initialTelemetryData.logger && initialTelemetryData.logger['error.count']) { - initialTotalCount = initialTelemetryData.logger['error.count']; - } + // Get the initial total error count + let initialTotalCount = 0; + if (initialTelemetryData.logger && initialTelemetryData.logger['error.count']) { + initialTotalCount = initialTelemetryData.logger['error.count']; + } - // The total error count should've increased with 6 - assert.strictEqual(newTelemetryData.logger['error.count'], initialTotalCount + 6); + // The total error count should've increased with 6 + assert.strictEqual(newTelemetryData.logger['error.count'], initialTotalCount + 6); - // The error count for this specific logger should be 6 - const telemetryName = util.format('error.%s.count', loggerName); - assert.strictEqual(newTelemetryData.logger[telemetryName], 6); - return callback(); - }); - }); + // The error count for this specific logger should be 6 + const telemetryName = util.format('error.%s.count', loggerName); + assert.strictEqual(newTelemetryData.logger[telemetryName], 6); + return callback(); + }); }); + }); }); diff --git a/packages/oae-preview-processor/lib/processors/file/pdf.js b/packages/oae-preview-processor/lib/processors/file/pdf.js index 9ac41a9763..fc48660d09 100644 --- a/packages/oae-preview-processor/lib/processors/file/pdf.js +++ b/packages/oae-preview-processor/lib/processors/file/pdf.js @@ -13,6 +13,8 @@ * permissions and limitations under the License. */ +import domStubs from './domstubs'; + const fs = require('fs'); const util = require('util'); @@ -44,8 +46,10 @@ ReadableSVGStream.prototype._read = function() { return; } } + this.push(null); }; + util.inherits(ReadableSVGStream, stream.Readable); /** @@ -62,6 +66,7 @@ const init = function(config, callback) { msg: 'Missing configuration for the pdf preview / processing' }); } + viewportScale = OaeUtil.getNumberParam(config.pdfPreview.viewportScale, viewportScale); return callback(); }; @@ -70,10 +75,7 @@ const init = function(config, callback) { * @borrows Interface.test as PDF.test */ const test = function(ctx, contentObj, callback) { - if ( - contentObj.resourceSubType === RESOURCE_SUBTYPE && - PreviewConstants.TYPES.PDF.indexOf(ctx.revision.mime) !== -1 - ) { + if (contentObj.resourceSubType === RESOURCE_SUBTYPE && PreviewConstants.TYPES.PDF.indexOf(ctx.revision.mime) !== -1) { callback(null, 10); } else { callback(null, -1); @@ -105,7 +107,7 @@ const generatePreviews = function(ctx, contentObj, callback) { * @param {Object} callback.err An error that occurred, if any */ const previewPDF = async function(ctx, pdfPath, callback) { - require('./domstubs.js').setStubs(global); + domStubs.setStubs(global); const pagesDir = path.join(ctx.baseDir, PAGES_SUBDIRECTORY); const output = path.join(pagesDir, TXT_CONTENT_FILENAME); @@ -134,9 +136,9 @@ const previewPDF = async function(ctx, pdfPath, callback) { await fsWriteFile(output, pdfContents.join(' ')); _generateThumbnail(ctx, pdfPath, pagesDir, callback); - } catch (e) { + } catch (error) { const errorMessage = 'Unable to process PDF'; - log().error({ e }, errorMessage); + log().error({ error }, errorMessage); return callback({ code: 500, msg: errorMessage }); } }; @@ -197,6 +199,7 @@ function ReadableSVGStream(options) { if (!(this instanceof ReadableSVGStream)) { return new ReadableSVGStream(options); } + stream.Readable.call(this, options); this.serializer = options.svgElement.getSerializer(); } @@ -218,10 +221,10 @@ function writeSvgToFile(svgElement, filePath) { writableStream.once('error', reject); writableStream.once('finish', resolve); readableSvgStream.pipe(writableStream); - }).catch(err => { + }).catch(error => { readableSvgStream = null; // Explicitly null because of v8 bug 6512. writableStream.end(); - throw err; + throw error; }); } @@ -256,10 +259,10 @@ const previewAndIndexEachPage = async function(ctx, pagesDir, pageNum, doc) { ctx.addPreview(pagePath, pageName); return fsWriteFile(pagePath, pageContents); - } catch (e) { + } catch (error) { const errorMessage = `Preview processing for pdf page ${pageNum} file failed`; - log().error({ e }, errorMessage); - throw e; + log().error({ error }, errorMessage); + throw error; } }; diff --git a/packages/oae-tests/lib/util.js b/packages/oae-tests/lib/util.js index c8d3e97e59..a39d4c17b1 100644 --- a/packages/oae-tests/lib/util.js +++ b/packages/oae-tests/lib/util.js @@ -49,7 +49,7 @@ const { Tenant } = require('oae-tenants/lib/model'); const TenantsTestUtil = require('oae-tenants/lib/test/util'); const { User } = require('oae-principals/lib/model'); -const migrationRunner = require(path.join(process.cwd(), 'etc/migration/migration_runner.js')); +const migrationRunner = require(path.join(process.cwd(), 'etc/migration/migration-runner.js')); const log = require('oae-logger').logger('before-tests'); diff --git a/packages/oae-tests/runner/beforeTests.js b/packages/oae-tests/runner/before-tests.js similarity index 94% rename from packages/oae-tests/runner/beforeTests.js rename to packages/oae-tests/runner/before-tests.js index 8c9e53b543..cd4e7a3c2c 100644 --- a/packages/oae-tests/runner/beforeTests.js +++ b/packages/oae-tests/runner/before-tests.js @@ -13,7 +13,11 @@ * permissions and limitations under the License. */ -/* eslint-disable unicorn/filename-case */ +import TestsUtil from 'oae-tests/lib/util'; +import { logger } from 'oae-logger'; + +const log = logger('before-tests'); + // eslint-disable-next-line no-unused-vars const { argv } = require('optimist') .usage('Run the Hilary tests.\nUsage: $0') @@ -24,10 +28,6 @@ const { argv } = require('optimist') process.env.OAE_BOOTSTRAP_LOG_LEVEL = 'trace'; process.env.OAE_BOOTSTRAP_LOG_FILE = './tests.log'; -const log = require('oae-logger').logger('before-tests'); - -const TestsUtil = require('oae-tests/lib/util'); - // Determine whether or not we should drop the keyspace before the test. In cases // where we want to set up the schema by another means (e.g., to test unit tests // over migrations), it is handy to use a schema that was pre-arranged for the diff --git a/packages/oae-tests/runner/init.js b/packages/oae-tests/runner/init.js new file mode 100644 index 0000000000..1c759ada49 --- /dev/null +++ b/packages/oae-tests/runner/init.js @@ -0,0 +1,4 @@ +// eslint-disable-next-line no-global-assign +require = require('esm')(module /* , options */); +// Import the rest of our application. +module.exports = require('./before-tests.js'); diff --git a/packages/oae-util/lib/modules.js b/packages/oae-util/lib/modules.js index 4f7e7cb024..3f9f602275 100644 --- a/packages/oae-util/lib/modules.js +++ b/packages/oae-util/lib/modules.js @@ -43,6 +43,7 @@ const bootstrapModules = function(config, callback) { if (err) { return callback(err); } + if (_.isEmpty(modules)) { return callback(new Error('No modules to install, or error aggregating modules.')); } @@ -54,6 +55,7 @@ const bootstrapModules = function(config, callback) { if (err) { return callback(err); } + // Register all endpoints return bootstrapModulesRest(modules, callback); }); @@ -84,6 +86,7 @@ const bootstrapModulesInit = function(modules, config, callback) { log().error({ err }, 'Error initializing module %s', moduleName); return callback(err); } + log().info('Initialized module %s', moduleName); done(); }); @@ -95,6 +98,7 @@ const bootstrapModulesInit = function(modules, config, callback) { if (err) { callback(err); } + callback(null); } ); @@ -116,6 +120,7 @@ const bootstrapModulesRest = function(modules, callback) { log().info('REST services for %s have been registered', module); require(module + '/lib/rest'); } + // Swagger document all modules return Swagger.documentModule(module, complete); }); diff --git a/packages/oae-util/lib/oae.js b/packages/oae-util/lib/oae.js index 1f9a5d22ba..dce09f2b5b 100644 --- a/packages/oae-util/lib/oae.js +++ b/packages/oae-util/lib/oae.js @@ -13,8 +13,9 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const log = require('oae-logger').logger('oae'); +import { logger } from 'oae-logger'; + +const log = logger(); const Modules = require('./modules'); const OaeEmitter = require('./emitter'); const Server = require('./server'); @@ -73,13 +74,9 @@ const init = function(config, callback) { // Start up the global and tenant servers globalAdminServer = Server.setupServer(config.servers.globalAdminPort, config); - module.exports.globalAdminServer = globalAdminServer; tenantServer = Server.setupServer(config.servers.tenantPort, config); - module.exports.tenantServer = tenantServer; tenantRouter = Server.setupRouter(tenantServer); - module.exports.tenantRouter = tenantRouter; globalAdminRouter = Server.setupRouter(globalAdminServer); - module.exports.globalAdminRouter = globalAdminRouter; // Initialize the modules and their CFs, as well as registering the Rest endpoints Modules.bootstrapModules(config, err => { @@ -114,7 +111,4 @@ const registerPreShutdownHandler = function(name, maxTimeMillis, handler) { Shutdown.registerPreShutdownHandler(name, maxTimeMillis, handler); }; -_.extend(module.exports, { - init, - registerPreShutdownHandler -}); +export { globalAdminServer, tenantServer, tenantRouter, globalAdminRouter, init, registerPreShutdownHandler }; diff --git a/packages/oae-util/lib/server.js b/packages/oae-util/lib/server.js index ba45bc2916..549607b457 100644 --- a/packages/oae-util/lib/server.js +++ b/packages/oae-util/lib/server.js @@ -172,8 +172,8 @@ const addSafePathPrefix = function(pathPrefix) { * This method is used to bind server functionality after all modules have had an opportunity to do so. This can be useful for things such * as: * - * * Response code logging / telemetry - * * Default "catch-all" error handling + * Response code logging / telemetry + * Default "catch-all" error handling * * @param {Express} app The express app for which the initialized should be finalized */ diff --git a/process.json b/process.json index 8b132e7d93..db5a370dd0 100644 --- a/process.json +++ b/process.json @@ -2,9 +2,10 @@ "apps": [ { "name": "Hilary", - "script": "app.js", + "script": "index.js", "cwd": "/opt/current", "watch": true, + "node_args": "-r esm", "instances": 1, "exec_mode": "cluster", "log_file": "/var/log/Hilary/hilary.log", From 462cbe73c5424bf86b25a17cbab62be16702be1c Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Fri, 29 Mar 2019 18:48:25 +0000 Subject: [PATCH 04/21] chore: update to ethercalc client --- config.js | 3 ++- package-lock.json | 5 ++--- package.json | 2 +- packages/oae-content/lib/internal/ethercalc.js | 10 ++++++++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/config.js b/config.js index bdd18b0107..c968a71752 100644 --- a/config.js +++ b/config.js @@ -443,7 +443,8 @@ config.etherpad = { config.ethercalc = { host: LOCALHOST, port: 8000, - protocol: 'http' + protocol: 'http', + timeout: 2500 }; /** * `config.tincanapi` diff --git a/package-lock.json b/package-lock.json index a4a2035e16..1023bd9cae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3337,9 +3337,8 @@ } }, "ethercalc-client": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/ethercalc-client/-/ethercalc-client-0.0.4.tgz", - "integrity": "sha1-iMVe7qrAqjZuWGflcsSlZXaftM8=", + "version": "git+https://github.com/oaeproject/ethercalc-client.git#c45ad7292a892a6fdb32d4343fc655ee5dadf205", + "from": "git+https://github.com/oaeproject/ethercalc-client.git", "requires": { "axios": "^0.18.0", "ethercalc": "^0.20170704.0" diff --git a/package.json b/package.json index 4a1886d21d..ac8e9a4fe9 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "elasticsearchclient": "^0.5.3", "ent": "^2.2.0", "esm": "^3.2.20", - "ethercalc-client": "^0.0.4", + "ethercalc-client": "https://github.com/oaeproject/ethercalc-client.git", "etherpad-lite-client": "^0.8.0", "expand-url": "^0.1.3", "express": "4.16.4", diff --git a/packages/oae-content/lib/internal/ethercalc.js b/packages/oae-content/lib/internal/ethercalc.js index 845515d790..6cee25cf0f 100644 --- a/packages/oae-content/lib/internal/ethercalc.js +++ b/packages/oae-content/lib/internal/ethercalc.js @@ -13,10 +13,11 @@ * permissions and limitations under the License. */ +import EthercalcClient from 'ethercalc-client'; + const url = require('url'); const _ = require('underscore'); const cheerio = require('cheerio'); -const Ethercalc = require('ethercalc-client'); const log = require('oae-logger').logger('ethercalc'); @@ -39,7 +40,12 @@ const TABLE_ELEMENT = 'table'; const refreshConfiguration = function(_ethercalcConfig) { // Remember this config. ethercalcConfig = _ethercalcConfig; - ethercalc = new Ethercalc(ethercalcConfig.host, ethercalcConfig.port, ethercalcConfig.protocol); + ethercalc = new EthercalcClient( + ethercalcConfig.host, + ethercalcConfig.port, + ethercalcConfig.protocol, + ethercalcConfig.timeout + ); }; /** From 766370210ed862cde67eaedf02a7614e390becc1 Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Sun, 31 Mar 2019 10:59:16 +0100 Subject: [PATCH 05/21] ci: fix for CCI to run --- .circleci/config.yml | 4 ++-- docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index db19fd2e6b..e3ab9ec6c1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -46,8 +46,8 @@ jobs: - run: name: Install docker and docker-compose command: | - apk add --update --no-cache docker py-pip - pip install docker-compose + apk add --update --no-cache docker py-pip python-dev libffi-dev openssl-dev gcc libc-dev make + pip install docker-compose~=1.23.2 - run: name: Create the containers command: docker-compose up --no-start --build oae-cassandra oae-redis oae-rabbitmq oae-elasticsearch oae-hilary oae-ethercalc diff --git a/docker-compose.yml b/docker-compose.yml index 06d2f41974..47ad4416bf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -134,7 +134,7 @@ services: tty: false oae-ethercalc: container_name: oae-ethercalc - image: "oaeproject/oae-ethercalc-docker" + image: oaeproject/oae-ethercalc-docker restart: always networks: - my_network From 5b7ac7e09c33f0a1f61e52378e1132c913320ef9 Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Mon, 1 Apr 2019 09:16:04 +0100 Subject: [PATCH 06/21] chore: update to oae-rest submodule --- packages/oae-rest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/oae-rest b/packages/oae-rest index dae944297f..e417f8615a 160000 --- a/packages/oae-rest +++ b/packages/oae-rest @@ -1 +1 @@ -Subproject commit dae944297ffa0a4028730bc93ae08b06d6a7a3e8 +Subproject commit e417f8615ab4d603c188f6eb1ea39a593e85ad5b From cd3e3e7b5d7949e8bff757c6c397d33c0883e0ac Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Tue, 2 Apr 2019 15:50:54 +0100 Subject: [PATCH 07/21] chore: small enhancement --- packages/oae-content/lib/api.js | 16 +++++++++------- .../oae-content/lib/internal/dao.content.js | 18 +++++++++++------- packages/oae-content/lib/search.js | 11 +++++------ .../lib/processors/collabdoc/collabdoc.js | 12 +++++++----- 4 files changed, 32 insertions(+), 25 deletions(-) diff --git a/packages/oae-content/lib/api.js b/packages/oae-content/lib/api.js index d11679a654..51a9a3e061 100644 --- a/packages/oae-content/lib/api.js +++ b/packages/oae-content/lib/api.js @@ -13,6 +13,8 @@ * permissions and limitations under the License. */ +import { isResourceACollabDoc, isResourceACollabSheet } from 'oae-content/lib/backends/util'; + const fs = require('fs'); const path = require('path'); const util = require('util'); @@ -186,7 +188,7 @@ const _getFullContentProfile = function(ctx, contentObj, isManager, callback) { contentObj.canShare = canShare; // For any other than collabdoc or collabsheet, we simply return with the share information - if ((contentObj.resourceSubType !== COLLABDOC) & (contentObj.resourceSubType !== COLLABSHEET)) { + if (!isResourceACollabDoc(contentObj.resourceSubType) && !isResourceACollabSheet(contentObj.resourceSubType)) { emitter.emit(ContentConstants.events.GET_CONTENT_PROFILE, ctx, contentObj); return callback(null, contentObj); } @@ -692,7 +694,7 @@ const _createContent = function( // Ensure all roles applied are valid. Editor is only valid for collabdocs const validRoles = [AuthzConstants.role.VIEWER, AuthzConstants.role.MANAGER]; - if (resourceSubType === COLLABDOC || resourceSubType === COLLABSHEET) { + if (isResourceACollabDoc(resourceSubType) || isResourceACollabSheet(resourceSubType)) { validRoles.push(AuthzConstants.role.EDITOR); } @@ -1170,7 +1172,7 @@ const joinCollabDoc = function(ctx, contentId, callback) { return callback(err); } - if (contentObj.resourceSubType === COLLABDOC) { + if (isResourceACollabDoc(contentObj.resourceSubType)) { // Join the pad Etherpad.joinPad(ctx, contentObj, (err, data) => { if (err) { @@ -1185,7 +1187,7 @@ const joinCollabDoc = function(ctx, contentId, callback) { return callback(null, { url: data.url }); }); }); - } else if (contentObj.resourceSubType === COLLABSHEET) { + } else if (isResourceACollabSheet(contentObj.resourceSubType)) { Ethercalc.joinRoom(ctx, contentObj, function(err, data) { if (err) { return callback(err); @@ -1246,7 +1248,7 @@ const deleteContent = function(ctx, contentId, callback) { return callback(err); } - if (contentObj.resourceSubType === COLLABSHEET) { + if (isResourceACollabSheet(contentObj.resourceSubType)) { Ethercalc.deleteRoom(contentObj.ethercalcRoomId, function(err) { if (err) { return callback(err); @@ -1392,7 +1394,7 @@ const setContentPermissions = function(ctx, contentId, changes, callback) { // Ensure all roles applied are valid. Editor is only valid for collabdocs and collabsheets const validRoles = [AuthzConstants.role.VIEWER, AuthzConstants.role.MANAGER]; - if (content.resourceSubType === COLLABDOC || content.resourceSubType === COLLABSHEET) { + if (isResourceACollabDoc(content.resourceSubType) || isResourceACollabSheet(content.resourceSubType)) { validRoles.push(AuthzConstants.role.EDITOR); } @@ -2758,7 +2760,7 @@ const restoreRevision = function(ctx, contentId, revisionId, callback) { // If this piece of content is a collaborative document, // we need to set the text in etherpad. - if (contentObj.resourceSubType === COLLABDOC) { + if (isResourceACollabDoc(contentObj.resourceSubType)) { Etherpad.setHTML(contentObj.id, contentObj.etherpadPadId, revision.etherpadHtml, err => { if (err) { return callback(err); diff --git a/packages/oae-content/lib/internal/dao.content.js b/packages/oae-content/lib/internal/dao.content.js index 908404abf1..260412bdf9 100644 --- a/packages/oae-content/lib/internal/dao.content.js +++ b/packages/oae-content/lib/internal/dao.content.js @@ -13,6 +13,13 @@ * permissions and limitations under the License. */ +import { + isResourceACollabDoc, + isResourceACollabSheet, + isResourceALink, + isResourceAFile +} from 'oae-content/lib/backends/util'; + const util = require('util'); const _ = require('underscore'); @@ -27,9 +34,6 @@ const { Content } = require('oae-content/lib/model'); const { ContentConstants } = require('oae-content/lib/constants'); const RevisionsDAO = require('./dao.revisions'); -const COLLABDOC = 'collabdoc'; -const COLLABSHEET = 'collabsheet'; - /// //////////// // Retrieval // /// //////////// @@ -537,16 +541,16 @@ const _rowToContent = function(row) { hash.latestRevisionId, hash.previews ); - if (contentObj.resourceSubType === 'file') { + if (isResourceAFile(contentObj.resourceSubType)) { contentObj.filename = hash.filename; contentObj.size = hash.size ? parseInt(hash.size, 10) : 0; contentObj.mime = hash.mime; - } else if (contentObj.resourceSubType === 'link') { + } else if (isResourceALink(contentObj.resourceSubType)) { contentObj.link = hash.link; - } else if (contentObj.resourceSubType === COLLABDOC) { + } else if (isResourceACollabDoc(contentObj.resourceSubType)) { contentObj.etherpadGroupId = hash.etherpadGroupId; contentObj.etherpadPadId = hash.etherpadPadId; - } else if (contentObj.resourceSubType === COLLABSHEET) { + } else if (isResourceACollabSheet(contentObj.resourceSubType)) { contentObj.ethercalcRoomId = hash.ethercalcRoomId; } diff --git a/packages/oae-content/lib/search.js b/packages/oae-content/lib/search.js index f47ff0ff45..910c1404ab 100644 --- a/packages/oae-content/lib/search.js +++ b/packages/oae-content/lib/search.js @@ -13,6 +13,8 @@ * permissions and limitations under the License. */ +import { isResourceACollabDoc, isResourceACollabSheet } from 'oae-content/lib/backends/util'; + const fs = require('fs'); const util = require('util'); const _ = require('underscore'); @@ -29,9 +31,6 @@ const { ContentConstants } = require('oae-content/lib/constants'); const ContentDAO = require('oae-content/lib/internal/dao'); const ContentUtil = require('oae-content/lib/internal/util'); -const COLLABDOC = 'collabdoc'; -const COLLABSHEET = 'collabsheet'; - /** * Initializes the child search documents for the Content module * @@ -350,7 +349,7 @@ const _getRevisionItems = function(contentItems, callback) { // Check if we need to fetch revisions const revisionsToRetrieve = []; _.each(contentItems, content => { - if (content.resourceSubType === COLLABDOC || content.resourceSubType === COLLABSHEET) { + if (isResourceACollabDoc(content.resourceSubType) || isResourceACollabSheet(content.resourceSubType)) { revisionsToRetrieve.push(content.latestRevisionId); } }); @@ -429,9 +428,9 @@ const _getContentItems = function(resources, callback) { const _produceContentSearchDocument = function(content, revision) { // Allow full-text search on name and description, but only if they are specified. We also sort on this text let fullText = _.compact([content.displayName, content.description]).join(' '); - if (content.resourceSubType === COLLABDOC && revision && revision.etherpadHtml) { + if (isResourceACollabDoc(content.resourceSubType) && revision && revision.etherpadHtml) { fullText += ' ' + revision.etherpadHtml; - } else if (content.resourceSubType === COLLABSHEET && revision && revision.ethercalcHtml) { + } else if (isResourceACollabSheet(content.resourceSubType) && revision && revision.ethercalcHtml) { fullText += ` ${revision.ethercalcHtml}`; } diff --git a/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js b/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js index 0e97586c1d..5f21d8aa26 100644 --- a/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js +++ b/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js @@ -13,6 +13,8 @@ * permissions and limitations under the License. */ +import { isResourceACollabDoc, isResourceACollabSheet } from 'oae-content/lib/backends/util'; + const fs = require('fs'); const Path = require('path'); const _ = require('underscore'); @@ -34,7 +36,6 @@ const screenShottingOptions = { } }; const COLLABDOC = 'collabdoc'; -const COLLABSHEET = 'collabsheet'; /** * Initializes the CollabDocProcessor @@ -70,7 +71,7 @@ const FILE_URI = 'file://'; * @borrows Interface.test as CollabDocProcessor.test */ const test = function(ctx, contentObj, callback) { - if (contentObj.resourceSubType === COLLABDOC || contentObj.resourceSubType === COLLABSHEET) { + if (isResourceACollabDoc(contentObj.resourceSubType) || isResourceACollabSheet(contentObj.resourceSubType)) { callback(null, 10); } else { callback(null, -1); @@ -96,7 +97,7 @@ const generatePreviews = function(ctx, contentObj, callback) { // Store whether this document is a collaborative document or spreadsheet const type = contentObj.resourceSubType; - const html = type === COLLABDOC ? 'etherpadHtml' : 'ethercalcHtml'; + const html = isResourceACollabDoc(contentObj.resourceSubType) ? 'etherpadHtml' : 'ethercalcHtml'; // Write the HTML to an HTML file, so a screenshot can be generated as the preview _writeCollabHtml(ctx, contentObj.latestRevision[html], type, function(err, collabFilePath) { @@ -176,8 +177,9 @@ const _writeCollabHtml = function(ctx, collabHtml, type, callback) { } // Write the resulting HTML to a temporary file on disk - const collabFilePath = - type === COLLABDOC ? Path.join(ctx.baseDir, '/etherpad.html') : Path.join(ctx.baseDir, '/ethercalc.html'); + const collabFilePath = isResourceACollabDoc(type) + ? Path.join(ctx.baseDir, '/etherpad.html') + : Path.join(ctx.baseDir, '/ethercalc.html'); fs.writeFile(collabFilePath, wrappedHtml, err => { if (err) { log().error({ err, contentId: ctx.contentId }, 'Could not write the collaborative file preview HTML to disk'); From 5c7105058fc48ab1ba633cbfe95136d52a873fb4 Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Tue, 2 Apr 2019 15:51:30 +0100 Subject: [PATCH 08/21] style: more linting --- packages/oae-util/lib/swagger.js | 62 ++++++++++++++++---------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/packages/oae-util/lib/swagger.js b/packages/oae-util/lib/swagger.js index eb9001ded5..b95a19a915 100644 --- a/packages/oae-util/lib/swagger.js +++ b/packages/oae-util/lib/swagger.js @@ -87,18 +87,21 @@ const documentModule = function(moduleName, callback) { if (err.code !== 'ENOENT') { log().warn({ err }, 'Problem recursing directories while documenting ' + moduleName); } + return callback(); }) .on('end', () => { if (_.isEmpty(files)) { return callback(); } + const done = _.after(files.length, callback); _.each(files, file => { register(file, err => { if (err) { log().warn({ err }, 'Problem opening a file while documenting ' + moduleName); } + return done(); }); }); @@ -117,6 +120,7 @@ const register = function(filePath, callback) { if (err) { return callback(err); } + try { const doc = restjsdoc.parse(data); // Add all models to both servers @@ -126,23 +130,23 @@ const register = function(filePath, callback) { // RestJSDoc arrays look like `type[]` so unArray will just be the bare `type` const unArray = property.type.replace(/\[\]$/, ''); /*! - * If the property.type doesn't match unArray then it was an array so we need to transform it to - * swagger model notation like: - * - * property = { - * 'type': 'array', - * 'items': { 'type': 'string' } - * } - * - * for primitive types and: - * - * property = { - * 'type': 'array', - * 'items': { '$ref': 'Model' } - * } - * - * for complex types - */ + * If the property.type doesn't match unArray then it was an array so we need to transform it to + * swagger model notation like: + * + * property = { + * 'type': 'array', + * 'items': { 'type': 'string' } + * } + * + * for primitive types and: + * + * property = { + * 'type': 'array', + * 'items': { '$ref': 'Model' } + * } + * + * for complex types + */ if (property.type !== unArray) { property.type = 'array'; if (_.contains(Constants.primitives, unArray)) { @@ -151,6 +155,7 @@ const register = function(filePath, callback) { property.items = { $ref: unArray }; } } + if (property.validValues) { property.enum = property.validValues; } @@ -164,10 +169,9 @@ const register = function(filePath, callback) { if (endpoint.api === 'private') { return; } + endpoint.summary = endpoint.description; - endpoint.responseClass = endpoint.return - ? _convertRJDArrayDefToSwagger(endpoint.return.type) - : 'void'; + endpoint.responseClass = endpoint.return ? _convertRJDArrayDefToSwagger(endpoint.return.type) : 'void'; endpoint.parameters = []; _.each(endpoint.pathParams, pathParam => { @@ -240,15 +244,14 @@ const register = function(filePath, callback) { } else if (server === 'admin') { _addSwaggerEndpoint(endpoint, adminResources); } else { - log().warn( - 'Tried to register swagger docs for unknown server "' + endpoint.server + '"' - ); + log().warn('Tried to register swagger docs for unknown server "' + endpoint.server + '"'); } }); }); } catch (error) { log().warn({ err: error }, util.format('Could not parse restjsdoc in %s', filePath)); } + return callback(); }); }; @@ -313,9 +316,7 @@ const _appendToApi = function(rootResource, api, spec) { // eslint-disable-next-line no-undef .isIn(Swagger.Constants.paramTypes); if (param.paramType === 'path') { - validator - .check(param.name, { path: api.path, name: param.name, msg: 'Invalid path' }) - .isIn(api.path); + validator.check(param.name, { path: api.path, name: param.name, msg: 'Invalid path' }).isIn(api.path); } }); @@ -340,6 +341,7 @@ const _convertRJDArrayDefToSwagger = function(def) { if (def.match(/\[\]$/)) { def = util.format('List[%s]', def.slice(0, -2)); } + return def; }; @@ -436,17 +438,13 @@ const _recurseModel = function(resource, modelName) { // The referenced type was either a primitive or it referenced a model that doesn't exist. No need to recursively look for model references return; } + if (resource.models[modelName]) { // This model type has already been visited. Don't recurse over it again or else we'll have an infinite loop return; } - log().trace( - { resource, model }, - 'Recursively adding model "%s" to resource "%s"', - modelName, - resource.path - ); + log().trace({ resource, model }, 'Recursively adding model "%s" to resource "%s"', modelName, resource.path); resource.models[modelName] = model; _.each(model.properties, property => { const { type } = property; From 64729e9875f6bfe50f879d6c6ab09a507d6d8c98 Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Wed, 3 Apr 2019 10:44:26 +0100 Subject: [PATCH 09/21] chore: update submodule 3akai-ux --- 3akai-ux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3akai-ux b/3akai-ux index f85bc11486..219345c1cc 160000 --- a/3akai-ux +++ b/3akai-ux @@ -1 +1 @@ -Subproject commit f85bc11486231b91243ea0f0f55df3729f41f4f4 +Subproject commit 219345c1ccf99298038863142260f3e87dc91455 From 643043ae2240f52521e21e5f6d62722307e3f72d Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Wed, 3 Apr 2019 12:26:40 +0100 Subject: [PATCH 10/21] chore: update submodule restjsdoc --- packages/restjsdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/restjsdoc b/packages/restjsdoc index 0d7be4e594..b8d6c0a83a 160000 --- a/packages/restjsdoc +++ b/packages/restjsdoc @@ -1 +1 @@ -Subproject commit 0d7be4e594d48c02a685ef56cf8bf9cec1c5c371 +Subproject commit b8d6c0a83a9410e9c02f49f3aa9e189718f1e57a From 496e9e559311dd704dcfa138348dde66540e32d7 Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Wed, 3 Apr 2019 21:59:53 +0100 Subject: [PATCH 11/21] feat: design of the new oae-version module --- package-lock.json | 666 ++++++++++++++++++++- package.json | 3 +- packages/oae-lti/lib/api.js | 13 +- packages/oae-version/lib/api.js | 359 ++--------- packages/oae-version/lib/init.js | 7 +- packages/oae-version/lib/rest.js | 24 +- packages/oae-version/tests/test-version.js | 79 ++- 7 files changed, 771 insertions(+), 380 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1023bd9cae..9fdea740e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -514,11 +514,59 @@ } } }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -926,6 +974,14 @@ "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.2.tgz", "integrity": "sha1-uJVivWmUr5W6HoEhVVNjM6ojzyQ=" }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "requires": { + "inherits": "~2.0.0" + } + }, "bluebird": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", @@ -1060,6 +1116,20 @@ "resolved": "https://registry.npmjs.org/buf-compare/-/buf-compare-1.0.1.tgz", "integrity": "sha1-/vKNqLgROgoNtEMLC2Rntpcws0o=" }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, "buffer-crc32": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.3.tgz", @@ -1070,6 +1140,11 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -1263,6 +1338,11 @@ "upath": "^1.1.0" } }, + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" + }, "ci-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", @@ -1823,6 +1903,11 @@ } } }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, "contains-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", @@ -2246,6 +2331,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=" }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -2268,6 +2358,11 @@ "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", "dev": true }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "diff": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", @@ -2463,7 +2558,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -4075,6 +4169,36 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + } + } + }, + "fs-minipass": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "requires": { + "minipass": "^2.2.1" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4628,6 +4752,24 @@ } } }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + } + } + }, "ftp": { "version": "0.3.10", "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", @@ -4767,6 +4909,46 @@ } } }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, "generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -5712,6 +5894,11 @@ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", "dev": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -5915,6 +6102,14 @@ "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", "dev": true }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "requires": { + "minimatch": "^3.0.4" + } + }, "image-size": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", @@ -5925,6 +6120,11 @@ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, + "immutable": { + "version": "4.0.0-rc.12", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0-rc.12.tgz", + "integrity": "sha512-0M2XxkZLx/mi3t8NVwIm1g8nHoEmM9p9UBl/G9k4+hm0kBgOVdMV/B3CY5dQ8qG8qc80NN4gDV4HQv6FTJ5q7A==" + }, "import-fresh": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", @@ -6726,6 +6926,22 @@ } } }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "optional": true + } + } + }, "jsonlint": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.0.tgz", @@ -7645,6 +7861,30 @@ "is-plain-obj": "^1.1.0" } }, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + } + } + }, + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "requires": { + "minipass": "^2.2.1" + } + }, "mixin-deep": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", @@ -8076,8 +8316,7 @@ "nan": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", - "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", - "optional": true + "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==" }, "nanoid": { "version": "2.0.1", @@ -8233,11 +8472,247 @@ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", "integrity": "sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==" }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "requires": { + "abbrev": "1" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + } + } + }, + "node-pre-gyp": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", + "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + }, + "dependencies": { + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "tar": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + } + } + }, "node-uuid": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" }, + "nodegit": { + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/nodegit/-/nodegit-0.24.1.tgz", + "integrity": "sha512-cEFecwEBsxjOCSpZyuCV3oEnHsE9PKyCpSi7oHdAt7y4izGoXUhanAqPvvuE71Mv96uc2/oTFYetGH9Z+Moq2g==", + "requires": { + "fs-extra": "^7.0.0", + "json5": "^2.1.0", + "lodash": "^4.17.11", + "nan": "^2.11.1", + "node-gyp": "^3.8.0", + "node-pre-gyp": "^0.11.0", + "promisify-node": "~0.3.0", + "ramda": "^0.25.0", + "request-promise-native": "^1.0.5", + "tar-fs": "^1.16.3" + }, + "dependencies": { + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "requires": { + "minimist": "^1.2.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "nodegit-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nodegit-promise/-/nodegit-promise-4.0.0.tgz", + "integrity": "sha1-VyKxhPLfcycWEGSnkdLoQskWezQ=", + "requires": { + "asap": "~2.0.3" + } + }, "nodemailer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-2.4.1.tgz", @@ -8505,6 +8980,20 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "npm-bundled": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" + }, + "npm-packlist": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -8513,6 +9002,17 @@ "path-key": "^2.0.0" } }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "nth-check": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", @@ -9995,6 +10495,11 @@ "resolved": "https://registry.npmjs.org/optparse/-/optparse-1.0.3.tgz", "integrity": "sha1-L/SaPWkbkLC5ob6RF/KSNz6xvWY=" }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, "os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -10017,6 +10522,15 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -10596,6 +11110,14 @@ "resolved": "https://registry.npmjs.org/promise-breaker/-/promise-breaker-4.1.13.tgz", "integrity": "sha512-+lGBqmBEgyvKweIrK4smdN1YxdYp5YjSL1us2XhTMBbZf98jdeGys/Edt5S1b1NXMVRQrvh4DrMgGpYPbXZf3g==" }, + "promisify-node": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/promisify-node/-/promisify-node-0.3.0.tgz", + "integrity": "sha1-tLVaz5D6p9K4uQyjlomQhsAwYM8=", + "requires": { + "nodegit-promise": "~4.0.0" + } + }, "propagate": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz", @@ -10767,6 +11289,11 @@ "resolved": "https://registry.npmjs.org/rails-timezone/-/rails-timezone-1.1.0.tgz", "integrity": "sha1-M88Hd8xaFwRMrkM4uZs3OifJ68A=" }, + "ramda": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", + "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==" + }, "random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -11121,11 +11648,36 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", - "dev": true, "requires": { "lodash": "^4.17.11" } }, + "request-promise-native": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "requires": { + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -11486,8 +12038,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-immediate-shim": { "version": "1.0.1", @@ -12467,8 +13018,7 @@ "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" }, "stream-counter": { "version": "0.2.0", @@ -12659,6 +13209,95 @@ } } }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + }, + "tar-fs": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", + "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", + "requires": { + "chownr": "^1.0.1", + "mkdirp": "^0.5.1", + "pump": "^1.0.0", + "tar-stream": "^1.1.2" + }, + "dependencies": { + "pump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", + "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "requires": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "dependencies": { + "bl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "temp": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.0.tgz", @@ -12763,6 +13402,11 @@ "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.3.tgz", "integrity": "sha1-1F2txjY0F/YPKEdP6lDs3btPSZE=" }, + "to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -13032,6 +13676,11 @@ "crypto-random-string": "^1.0.0" } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -13494,7 +14143,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, "requires": { "string-width": "^1.0.2 || 2" } diff --git a/package.json b/package.json index ac8e9a4fe9..7213e8c6f9 100644 --- a/package.json +++ b/package.json @@ -43,9 +43,9 @@ "etherpad-lite-client": "^0.8.0", "expand-url": "^0.1.3", "express": "4.16.4", - "gift": "^0.10.1", "globalize": "0.1.1", "gm": "^1.23.0", + "immutable": "^4.0.0-rc.12", "jszip": "^3.2.1", "juice": "^5.2.0", "less": "1.7.5", @@ -59,6 +59,7 @@ "mobile-detect": "^1.3.7", "multiparty": "^4.1.3", "node-esapi": "^0.0.1", + "nodegit": "^0.24.1", "nodemailer": "2.4.1", "nodemailer-html-to-text": "^3.0.0", "nodemailer-sendmail-transport": "^1.0.2", diff --git a/packages/oae-lti/lib/api.js b/packages/oae-lti/lib/api.js index 47a927ea5d..54771620e9 100644 --- a/packages/oae-lti/lib/api.js +++ b/packages/oae-lti/lib/api.js @@ -53,16 +53,15 @@ const getLtiTool = function(ctx, id, groupId, callback) { return callback(err); } - VersionAPI.getVersion((err, version) => { + VersionAPI.getVersionCB((err, gitRepoInformation) => { if (err) { log().warn('Failed to fetch OAE version'); - version = { - hilary: { - version: '' - } - }; + version = ''; } + const hilaryRepoInfo = gitRepoInformation.get('Hilary'); + let version = hilaryRepoInfo.latestTag; + LtiDAO.getLtiTool(id, groupId, (err, tool) => { if (err) { log().error( @@ -78,7 +77,7 @@ const getLtiTool = function(ctx, id, groupId, callback) { const launchParams = new LtiLaunchParams( tool, - version.hilary.version, + version, group.tenant.alias, group.displayName, group.isManager, diff --git a/packages/oae-version/lib/api.js b/packages/oae-version/lib/api.js index a59d4d5208..1d541b7fe6 100644 --- a/packages/oae-version/lib/api.js +++ b/packages/oae-version/lib/api.js @@ -1,4 +1,5 @@ -/*! +/*! r + * Copyright 2015 Apereo Foundation (AF) Licensed under the * Educational Community License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may @@ -13,31 +14,14 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const path = require('path'); -const util = require('util'); -const gift = require('gift'); -const _ = require('underscore'); +import path from 'path'; +import { Map } from 'immutable'; +import git from 'nodegit'; -const IO = require('oae-util/lib/io'); -const log = require('oae-logger').logger('oae-version'); +import _ from 'underscore'; // A variable that will hold the path to the UI directory const hilaryDirectory = path.resolve(__dirname, '..', '..', '..'); -let _uiPath = null; -const FRONTEND_REPO = '3akai-ux'; - -// A variable that will hold a copy of the version information -let _version = null; - -/** - * Initialize the version module - * - * @param {String} uiPath The path to the UI directory - */ -const init = function(uiPath) { - _uiPath = uiPath; -}; /** * Get the version information for OAE @@ -48,313 +32,50 @@ const init = function(uiPath) { * @param {String} callback.version.hilary The version information for Hilary * @param {String} callback.version.3akai-ux The version information for the UI */ -const getVersion = function(callback) { - if (_version) { - return callback(null, _version); - } - - _getHilaryVersion(function(err, hilaryVersion) { - if (err) { - return callback(err); - } - - _getUIVersion(function(err, uiVersion) { - if (err) { - return callback(err); - } - - _version = { - hilary: hilaryVersion, - FRONTEND_REPO: uiVersion - }; - return callback(null, _version); - }); +const getVersionCB = function(callback) { + // eslint-disable-next-line promise/prefer-await-to-then + getVersion().then(version => { + return callback(null, version); }); }; -/** - * Get the version information for the backend - * - * @param {Function} callback Standard callback function - * @param {Object} callback.err An error that occurred, if any - * @param {String} callback.version The version information for the backend - * @api private - */ -function _getHilaryVersion(callback) { - _getVersionInfo(hilaryDirectory, callback); -} - -/** - * Get the 'version' for 3akai-ux, which mainly consists of the commit Hilary submodule is pointing to - * - * @param {Function} callback Standard callback function - * @param {Object} callback.err An error that occurred, if any - * @param {String} callback.version The version information for the UI - * @api private - */ -function _getUIVersion(callback) { - gift.init(hilaryDirectory, (err, repo) => { - repo.tree().contents((err, children) => { - // Find the 3akai-ux submodule within the tree - const submodulePointer = _.find(children, eachChild => { - return eachChild.name === FRONTEND_REPO; - }).id; - - // Now let's check if the submodule pointer corresponds to the last commit on 3akai-ux - // if it does, that means everything has been checked out properly - gift.init(path.resolve(hilaryDirectory, _uiPath), (err, submodule) => { - submodule.current_commit((err, lastCheckedOutCommit) => { - if (submodulePointer === lastCheckedOutCommit.id) { - const version = { - branch: 'HEAD detached at ' + lastCheckedOutCommit.id, - date: lastCheckedOutCommit.committed_date, - type: 'git submodule' - }; - return callback(null, version); - } - - return callback({ code: 500, msg: "The submodule hasn't been checked out properly" }); - }); - }); - }); - }); -} - -/** - * Get the version information for a directory. The version will be retrieved from - * the build info file in the root of the directory. If there's no such file present - * or it can't be parsed, the information will be retrieved from git. If that fails - * as well `Unknown` will be returned - * - * @param {String} directory The directory to get the version information for - * @param {Function} callback Standard callback function - * @param {Object} callback.err An error that occurred, if any - * @param {String} callback.version The version information for the directory - * @api private - */ -function _getVersionInfo(directory, callback) { - _getBuildInfoFile(directory, function(err, buildInfoPath) { - if (err) { - return callback(err); - } - - if (buildInfoPath) { - return _getBuildVersion(buildInfoPath, callback); - } - - return _getGitVersion(directory, callback); - }); -} - -/** - * Each generated build has a `build-info.json` file that contains information about the build. - * Parse it and return the relevant version information - * - * @param {String} buildInfoPath The path to the build info file - * @param {Function} callback Standard callback function - * @param {Object} callback.err An error that occurred, if any - * @param {String} callback.version The version information for the directory - * @api private - */ -function _getBuildVersion(buildInfoPath, callback) { - fs.readFile(buildInfoPath, function(err, buildInfo) { - if (err) { - return callback({ code: 500, msg: 'Unable to read the build info file' }); - } - - try { - buildInfo = JSON.parse(buildInfo); - } catch (error) { - log().error( - { - err: error, - path: buildInfoPath - }, - 'Unable to parse the build info file' - ); - return callback({ code: 500, msg: 'Unable to parse the build info file' }); - } - - buildInfo.type = 'archive'; - - return callback(null, buildInfo); - }); -} - -/** - * Get the version from git - * - * @param {String} directory The directory to the git repository - * @param {Function} callback Standard callback function - * @param {Object} callback.err An error that occurred, if any - * @param {String} callback.version The version information for the directory - * @api private - */ -function _getGitVersion(directory, callback) { - gift.init(directory, function(err, repo) { - if (err) { - log().error( - { - err, - path: directory - }, - 'Could not open the git repo to get the version information' - ); - return callback({ code: 500, msg: 'Could not open the git repo' }); - } - - // Get the tag info - repo.tags(function(err, tags) { - if (err) { - log().error( - { - err, - path: directory - }, - 'Could not get the git tags' - ); - return callback({ code: 500, msg: 'Could not get the git tags' }); - } - - const tagsByCommitId = _.indexBy(tags, function(tag) { - return tag.commit.id; - }); - - // Get the current branch info - repo.branch(function(err, branch) { - if (err) { - log().error( - { - err, - path: directory - }, - 'Could not get the current git branch for the version information' - ); - return callback({ code: 500, msg: 'Could not get the branch information' }); - } - - // Get the number of commits since the last tag - _getGitDescribeVersion(repo, branch.name, tagsByCommitId, function(err, describeVersion) { - if (err) { - log().error( - { - err, - path: directory - }, - 'Could not get the git describe version for a repo' - ); - return callback({ code: 500, msg: 'Could not get the branch information' }); - } - - const buildInfo = { - branch: branch.name, - date: branch.commit.committed_date, - type: 'git', - version: describeVersion - }; - return callback(null, buildInfo); - }); - }); - }); - }); -} - -/** - * Get a "git describe"-like descriptor for a branch. - * - * This will return information in the form: - * ++ - * - * @param {Repository} repo The git repository to get the information for - * @param {String} branchName The name of the branch to get the information for - * @param {Object} tagsByCommitId The Tag objects keyed by their commit sha1 hash - * @param {Function} callback Standard callback function - * @param {Object} callback.err An error that occurred, if any - * @param {String} callback.version The git describe information - * @api private - */ -function _getGitDescribeVersion(repo, branchName, tagsByCommitId, callback, _nrOfCommits, _lastCommitSha) { - _nrOfCommits = _nrOfCommits || 0; - repo.commits(branchName, 30, _nrOfCommits, function(err, commits) { - if (err) { - return callback(err); - } - - // Retain the sha1 of the last commit on this branch - _lastCommitSha = _lastCommitSha || commits[0].id; +const getVersion = async function(repoPath = hilaryDirectory, repoInformation = new Map()) { + const repo = await git.Repository.open(repoPath); + const headCommit = await repo.getHeadCommit(); - // Try to find the last tagged commit in these set of commits - let lastTag = null; - for (let i = 0; i < commits.length; i++) { - if (tagsByCommitId[commits[i].id]) { - lastTag = tagsByCommitId[commits[i].id].name; - break; - } + const lastCommitId = headCommit.id().toString(); + const lastCommitDate = headCommit.date(); - _nrOfCommits++; - } + const tags = await git.Tag.list(repo); - // If we found a tag in the set of commits we can return the git describe information - if (lastTag) { - const describeVersion = util.format('%s+%d+%s', lastTag, _nrOfCommits, _lastCommitSha); - return callback(null, describeVersion); - - // Otherwise we need to retrieve the next set of commits - } + const findLatestTag = (accumulator, currentValue) => { + return parseFloat(accumulator) > parseFloat(currentValue) ? accumulator : currentValue; + }; - return _getGitDescribeVersion(repo, branchName, tagsByCommitId, callback, _nrOfCommits, _lastCommitSha); - }); -} + const latestTag = tags.reduce(findLatestTag); -/** - * Check whether a directory has a build-info.json file. This function - * will recursively check each parent directory as well. - * - * @param {String} directory The directory to get the version information for - * @param {Function} callback Standard callback function - * @param {Object} callback.err An error that occurred, if any - * @param {String} [callback.buildInfoPath] The path to the build-info.json file. `null` if no such file could be found - * @api private - */ -function _getBuildInfoFile(directory, callback) { - const buildInfoPath = _getBuildInfoFilePath(directory); - IO.exists(buildInfoPath, function(err, exists) { - if (err) { - log.error( - { - directory, - err - }, - 'Unable to check if a build info file exists' - ); - return callback(err); - } - - if (exists) { - return callback(null, buildInfoPath); - } - - const parent = path.dirname(directory); - if (parent !== directory) { - return _getBuildInfoFile(parent, callback); + const submodulePointers = {}; + const submodules = await repo.getSubmoduleNames(); + if (!_.isEmpty(submodules)) { + for (let index = 0; index < submodules.length; index++) { + const eachSubmodule = submodules[index]; + // eslint-disable-next-line no-await-in-loop + const eachSubmoduleRepo = await git.Submodule.lookup(repo, eachSubmodule); + submodulePointers[eachSubmodule] = eachSubmoduleRepo.headId().toString(); + // eslint-disable-next-line no-await-in-loop + repoInformation = await getVersion(path.join(repoPath, eachSubmodule), repoInformation); } + } - return callback(); + const repoName = _.last(repoPath.split('/')); + repoInformation = repoInformation.set(repoName, { + lastCommitId, + lastCommitDate, + latestTag, + submodulePointers }); -} -/** - * Get the path to the `build-info.json` file - * - * @param {String} directory The directory to get the version information for - * @return {String} The path to the `build-info.json` file - * @api private - */ -function _getBuildInfoFilePath(directory) { - return path.join(directory, 'build-info.json'); -} - -module.exports = { - init, - getVersion + return repoInformation; }; + +export { getVersion, getVersionCB }; diff --git a/packages/oae-version/lib/init.js b/packages/oae-version/lib/init.js index 229c2cd298..bf19b26910 100644 --- a/packages/oae-version/lib/init.js +++ b/packages/oae-version/lib/init.js @@ -13,15 +13,14 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const VersionAPI = require('./api'); - /** * Initialize the version module * + * @param {Object} config The config object if any + * @param {Object} callback Standard callback function + * @returns {Object} function the callback from parameters * @see {oae-util/lib/oae} */ module.exports = function(config, callback) { - VersionAPI.init(fs.realpathSync(config.ui.path)); return callback(); }; diff --git a/packages/oae-version/lib/rest.js b/packages/oae-version/lib/rest.js index cb6e0135be..da122b4520 100644 --- a/packages/oae-version/lib/rest.js +++ b/packages/oae-version/lib/rest.js @@ -13,9 +13,19 @@ * permissions and limitations under the License. */ -var OAE = require('oae-util/lib/oae'); +import * as VersionAPI from './api'; -var VersionAPI = require('./api'); +const OAE = require('oae-util/lib/oae'); + +const _getVersion = async function(req, res) { + try { + const repoInformation = await VersionAPI.getVersion(); + return res.status(200).send(JSON.stringify(repoInformation)); + } catch (error) { + const msg = 'Unable to gather repo information'; + return res.status(500).send(msg); + } +}; /** * @REST getVersion @@ -30,13 +40,3 @@ var VersionAPI = require('./api'); */ OAE.tenantRouter.on('get', '/api/version', _getVersion); OAE.globalAdminRouter.on('get', '/api/version', _getVersion); - -function _getVersion(req, res) { - VersionAPI.getVersion(function(err, version) { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(200).send(version); - }); -} diff --git a/packages/oae-version/tests/test-version.js b/packages/oae-version/tests/test-version.js index 7d4ceaee6d..20c57471b0 100644 --- a/packages/oae-version/tests/test-version.js +++ b/packages/oae-version/tests/test-version.js @@ -13,39 +13,28 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import { fromJS } from 'immutable'; -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); +import _ from 'underscore'; -const FRONTEND_REPO = '3akai-ux'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; -describe('Version information', function() { +describe('Git information', function() { /** - * Test that verifies that the version information is returned + * Test that verifies that the git information is returned */ - it('should return the version information', function(callback) { + it('verify that the submodules exist and are up to date', function(callback) { // Create various rest contexts - const anonTenantRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); const adminTenantRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.cam.host); - const anonGlobalRestContext = TestsUtil.createGlobalRestContext(); - const globalAdminRestContext = TestsUtil.createGlobalAdminRestContext(); + const anonTenantRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); TestsUtil.generateTestUsers(adminTenantRestContext, 1, function(err, users, user) { assert.ok(!err); const userTenantRestContext = user.restContext; // Verify the version information on regular tenancies - _verifyVersionInformation(anonTenantRestContext, function() { - _verifyVersionInformation(userTenantRestContext, function() { - _verifyVersionInformation(adminTenantRestContext, function() { - // Verify the version information on the global admin - _verifyVersionInformation(anonGlobalRestContext, function() { - _verifyVersionInformation(globalAdminRestContext, callback); - }); - }); - }); - }); + _verifyVersionInformation(anonTenantRestContext, callback); }); }); @@ -57,14 +46,48 @@ describe('Version information', function() { * @throws {AssertionError} Thrown if any assertions fail */ function _verifyVersionInformation(restContext, callback) { - RestAPI.Version.getVersion(restContext, function(err, version) { + RestAPI.Version.getVersion(restContext, function(err, gitRepoInformation) { assert.ok(!err); - assert.ok(_.isObject(version)); - assert.strictEqual(_.size(version), 2); - assert.ok(_.isObject(version.hilary)); - assert.strictEqual(_.size(version.hilary), 4); - assert.ok(_.isObject(version[FRONTEND_REPO])); - assert.strictEqual(_.size(version[FRONTEND_REPO]), 3); + + const repoInfo = fromJS(gitRepoInformation); + + const hilaryInfo = repoInfo.get('Hilary'); + assert.ok(_.isObject(hilaryInfo)); + assert.ok(_.isString(hilaryInfo.get('lastCommitId'))); + assert.ok(_.isString(hilaryInfo.get('lastCommitDate'))); + assert.ok(_.isString(hilaryInfo.get('latestTag'))); + + const submodulePointers = hilaryInfo.get('submodulePointers'); + assert.ok(_.isObject(submodulePointers)); + assert.strictEqual(submodulePointers.size, 3); + + const frontendInfo = repoInfo.get('3akai-ux'); + assert.ok(_.isObject(frontendInfo)); + assert.ok(_.isString(frontendInfo.get('lastCommitId'))); + assert.ok(_.isString(frontendInfo.get('lastCommitDate'))); + assert.ok(_.isString(frontendInfo.get('latestTag'))); + assert.strictEqual(frontendInfo.get('submodulePointers').size, 0); + + const oaeRestInfo = repoInfo.get('oae-rest'); + assert.ok(_.isObject(oaeRestInfo)); + assert.ok(_.isString(oaeRestInfo.get('lastCommitId'))); + assert.ok(_.isString(oaeRestInfo.get('lastCommitDate'))); + assert.ok(_.isString(oaeRestInfo.get('latestTag'))); + assert.strictEqual(oaeRestInfo.get('submodulePointers').size, 0); + + const restjsDocInfo = repoInfo.get('restjsdoc'); + assert.ok(_.isObject(restjsDocInfo)); + assert.ok(_.isString(restjsDocInfo.get('lastCommitId'))); + assert.ok(_.isString(restjsDocInfo.get('lastCommitDate'))); + assert.ok(_.isString(restjsDocInfo.get('latestTag'))); + assert.strictEqual(restjsDocInfo.get('submodulePointers').size, 0); + + // Verify that the latest commit on every submodule repo is where Hilary is pointing + // this would mean that Hilary submodules are up to date + assert.strictEqual(submodulePointers.get('3akai-ux'), frontendInfo.get('lastCommitId')); + assert.strictEqual(submodulePointers.get('packages/oae-rest'), oaeRestInfo.get('lastCommitId')); + assert.strictEqual(submodulePointers.get('packages/restjsdoc'), restjsDocInfo.get('lastCommitId')); + callback(); }); } From a3616e18d999f2ee7e08c2a1a8d5bfbaa63b5bd0 Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Sat, 6 Apr 2019 11:54:20 +0100 Subject: [PATCH 12/21] build: add deps to nodegit --- Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index 08e335e50e..887626c069 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,6 +32,10 @@ LABEL Name=OAE-Hilary LABEL Author=ApereoFoundation LABEL Email=oae@apereo.org +# install nodegit +RUN apk --update --no-cache add build-base libgit2-dev +RUN ln -s /usr/lib/libcurl.so.4 /usr/lib/libcurl-gnutls.so.4 + # Set the base directory ENV HILARY_DIR usr/src/Hilary RUN mkdir -p ${HILARY_DIR} \ From 7f20905e8b5df038734f981b6dc95cfeaa54a30c Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Sat, 6 Apr 2019 12:39:16 +0100 Subject: [PATCH 13/21] fix: attempt to remove deps image from dockerfile --- Dockerfile | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 887626c069..47a004ebb3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,18 +26,41 @@ # $ docker run -it --name=hilary --net=host oae-hilary:latest # -FROM oaeproject/oae-hilary-deps-docker:v0.4 +FROM node:10-alpine LABEL Name=OAE-Hilary LABEL Author=ApereoFoundation LABEL Email=oae@apereo.org +RUN apk --update --no-cache add \ + git \ + python \ + ghostscript \ + graphicsmagick + +# Installs the 3.8 Chromium package. +RUN apk update && apk upgrade && \ + echo @3.8 http://nl.alpinelinux.org/alpine/v3.8/community >> /etc/apk/repositories && \ + echo @3.8 http://nl.alpinelinux.org/alpine/v3.8/main >> /etc/apk/repositories && \ + apk add --no-cache \ + chromium@3.8 \ + nss@3.8 \ + freetype@3.8 \ + harfbuzz@3.8 \ + ttf-freefont@3.8 + +# Tell Puppeteer to skip installing Chrome. We'll be using the installed package. +ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true + +# Install libreoffice +RUN apk add --no-cache libreoffice openjdk8-jre + # install nodegit RUN apk --update --no-cache add build-base libgit2-dev RUN ln -s /usr/lib/libcurl.so.4 /usr/lib/libcurl-gnutls.so.4 # Set the base directory -ENV HILARY_DIR usr/src/Hilary +ENV HILARY_DIR /usr/src/Hilary RUN mkdir -p ${HILARY_DIR} \ && chown -R node:node ${HILARY_DIR} \ && chmod -R 755 ${HILARY_DIR} From 0cf06d2651576d9d3413ce28520fcd9dc50b6b04 Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Mon, 8 Apr 2019 18:54:53 +0100 Subject: [PATCH 14/21] revert: remove some code re:ES6 modules with ESM --- app.js | 14 +- etc/migration/migration-runner.js | 22 +- migrate.js | 6 +- package.json | 4 +- packages/oae-content/lib/api.js | 22 +- .../oae-content/lib/internal/dao.content.js | 4 +- .../oae-content/lib/internal/ethercalc.js | 1 + packages/oae-content/lib/rest.js | 4 +- packages/oae-content/lib/search.js | 2 +- packages/oae-doc/lib/api.js | 19 +- packages/oae-doc/lib/init.js | 2 +- packages/oae-doc/lib/rest.js | 6 +- packages/oae-logger/lib/api.js | 8 +- packages/oae-logger/lib/init.js | 2 +- packages/oae-logger/tests/test-logger.js | 12 +- .../lib/processors/collabdoc/collabdoc.js | 2 +- .../oae-search/tests/test-tenants-search.js | 285 +++++++----------- packages/oae-tenants/lib/api.js | 48 +-- packages/oae-tests/runner/before-tests.js | 4 +- packages/oae-util/lib/oae.js | 8 +- packages/oae-version/lib/api.js | 10 +- packages/oae-version/lib/rest.js | 3 +- packages/oae-version/tests/test-version.js | 10 +- process.json | 2 +- 24 files changed, 217 insertions(+), 283 deletions(-) diff --git a/app.js b/app.js index c21f842a7a..06f1fc4dbf 100755 --- a/app.js +++ b/app.js @@ -15,14 +15,14 @@ * permissions and limitations under the License. */ -import path from 'path'; -import repl from 'repl'; -import PrettyStream from 'bunyan-prettystream'; -import optimist from 'optimist'; -import _ from 'underscore'; +const path = require('path'); +const repl = require('repl'); +const PrettyStream = require('bunyan-prettystream'); +const optimist = require('optimist'); +const _ = require('underscore'); -import * as OAE from 'oae-util/lib/oae'; -import { logger } from 'oae-logger'; +const OAE = require('oae-util/lib/oae'); +const { logger } = require('oae-logger'); const log = logger(); diff --git a/etc/migration/migration-runner.js b/etc/migration/migration-runner.js index 49cf841c10..aa372102a1 100644 --- a/etc/migration/migration-runner.js +++ b/etc/migration/migration-runner.js @@ -13,20 +13,22 @@ * permissions and limitations under the License. */ -import fs from 'fs'; -import path from 'path'; -import { promisify } from 'util'; -import { logger, refreshLogConfiguration } from 'oae-logger'; -import PrettyStream from 'bunyan-prettystream'; -import { eachSeries } from 'async'; import { config } from '../../config'; +const fs = require('fs'); +const path = require('path'); +const { promisify } = require('util'); +const PrettyStream = require('bunyan-prettystream'); +const { eachSeries } = require('async'); + +const LogAPI = require('oae-logger'); + const _createLogger = function(config) { const prettyLog = new PrettyStream(); prettyLog.pipe(process.stdout); config.log.streams[0].stream = prettyLog; - refreshLogConfiguration(config.log); - return logger(); + LogAPI.refreshLogConfiguration(config.log); + return LogAPI.logger(); }; const log = _createLogger(config); @@ -60,7 +62,7 @@ const lookForMigrations = async function(allModules) { return migrationsToRun; }; -export const runMigrations = async function(dbConfig, callback) { +const runMigrations = async function(dbConfig, callback) { log().info('Running migrations for keyspace ' + dbConfig.keyspace + '...'); const data = {}; @@ -98,3 +100,5 @@ export const runMigrations = async function(dbConfig, callback) { callback(error); } }; + +module.exports = { runMigrations }; diff --git a/migrate.js b/migrate.js index 542f343912..4e83c51ef4 100644 --- a/migrate.js +++ b/migrate.js @@ -14,10 +14,10 @@ * permissions and limitations under the License. */ -import optimist from 'optimist'; +const optimist = require('optimist'); -import { runMigrations } from './etc/migration/migration-runner'; -import { config } from './config'; +const { runMigrations } = require('./etc/migration/migration-runner'); +const { config } = require('./config'); const dbConfig = config.cassandra; diff --git a/package.json b/package.json index 7213e8c6f9..2ef6466f7d 100644 --- a/package.json +++ b/package.json @@ -158,8 +158,8 @@ "node": ">=10.13.0" }, "scripts": { - "test": "mocha -r esm 'node_modules/oae-tests/runner/init.js' 'node_modules/oae-*/tests/**/*.js'", - "test-module": "func() { mocha -r esm 'node_modules/oae-tests/runner/init.js' 'node_modules/'$1'/tests/**/*.js'; }; func", + "test": "mocha -r esm 'node_modules/oae-tests/runner/before-tests.js' 'node_modules/oae-*/tests/**/*.js'", + "test-module": "func() { mocha -r esm 'node_modules/oae-tests/runner/before-tests.js' 'node_modules/'$1'/tests/**/*.js'; }; func", "migrate": "func() { node -r esm 'migrate.js' '--keyspace' $1 ; }; func", "start": "node -r esm app.js | npx bunyan", "dev-server": "nodemon app.js | npx bunyan", diff --git a/packages/oae-content/lib/api.js b/packages/oae-content/lib/api.js index 51a9a3e061..7be6133570 100644 --- a/packages/oae-content/lib/api.js +++ b/packages/oae-content/lib/api.js @@ -13,11 +13,11 @@ * permissions and limitations under the License. */ -import { isResourceACollabDoc, isResourceACollabSheet } from 'oae-content/lib/backends/util'; const fs = require('fs'); const path = require('path'); const util = require('util'); +const ContentUtils = require('oae-content/lib/backends/util'); const _ = require('underscore'); const mime = require('mime'); const ShortId = require('shortid'); @@ -188,7 +188,10 @@ const _getFullContentProfile = function(ctx, contentObj, isManager, callback) { contentObj.canShare = canShare; // For any other than collabdoc or collabsheet, we simply return with the share information - if (!isResourceACollabDoc(contentObj.resourceSubType) && !isResourceACollabSheet(contentObj.resourceSubType)) { + if ( + !ContentUtils.isResourceACollabDoc(contentObj.resourceSubType) && + !ContentUtils.isResourceACollabSheet(contentObj.resourceSubType) + ) { emitter.emit(ContentConstants.events.GET_CONTENT_PROFILE, ctx, contentObj); return callback(null, contentObj); } @@ -694,7 +697,7 @@ const _createContent = function( // Ensure all roles applied are valid. Editor is only valid for collabdocs const validRoles = [AuthzConstants.role.VIEWER, AuthzConstants.role.MANAGER]; - if (isResourceACollabDoc(resourceSubType) || isResourceACollabSheet(resourceSubType)) { + if (ContentUtils.isResourceACollabDoc(resourceSubType) || ContentUtils.isResourceACollabSheet(resourceSubType)) { validRoles.push(AuthzConstants.role.EDITOR); } @@ -1172,7 +1175,7 @@ const joinCollabDoc = function(ctx, contentId, callback) { return callback(err); } - if (isResourceACollabDoc(contentObj.resourceSubType)) { + if (ContentUtils.isResourceACollabDoc(contentObj.resourceSubType)) { // Join the pad Etherpad.joinPad(ctx, contentObj, (err, data) => { if (err) { @@ -1187,7 +1190,7 @@ const joinCollabDoc = function(ctx, contentId, callback) { return callback(null, { url: data.url }); }); }); - } else if (isResourceACollabSheet(contentObj.resourceSubType)) { + } else if (ContentUtils.isResourceACollabSheet(contentObj.resourceSubType)) { Ethercalc.joinRoom(ctx, contentObj, function(err, data) { if (err) { return callback(err); @@ -1248,7 +1251,7 @@ const deleteContent = function(ctx, contentId, callback) { return callback(err); } - if (isResourceACollabSheet(contentObj.resourceSubType)) { + if (ContentUtils.isResourceACollabSheet(contentObj.resourceSubType)) { Ethercalc.deleteRoom(contentObj.ethercalcRoomId, function(err) { if (err) { return callback(err); @@ -1394,7 +1397,10 @@ const setContentPermissions = function(ctx, contentId, changes, callback) { // Ensure all roles applied are valid. Editor is only valid for collabdocs and collabsheets const validRoles = [AuthzConstants.role.VIEWER, AuthzConstants.role.MANAGER]; - if (isResourceACollabDoc(content.resourceSubType) || isResourceACollabSheet(content.resourceSubType)) { + if ( + ContentUtils.isResourceACollabDoc(content.resourceSubType) || + ContentUtils.isResourceACollabSheet(content.resourceSubType) + ) { validRoles.push(AuthzConstants.role.EDITOR); } @@ -2760,7 +2766,7 @@ const restoreRevision = function(ctx, contentId, revisionId, callback) { // If this piece of content is a collaborative document, // we need to set the text in etherpad. - if (isResourceACollabDoc(contentObj.resourceSubType)) { + if (ContentUtils.isResourceACollabDoc(contentObj.resourceSubType)) { Etherpad.setHTML(contentObj.id, contentObj.etherpadPadId, revision.etherpadHtml, err => { if (err) { return callback(err); diff --git a/packages/oae-content/lib/internal/dao.content.js b/packages/oae-content/lib/internal/dao.content.js index 260412bdf9..89c3f8a90b 100644 --- a/packages/oae-content/lib/internal/dao.content.js +++ b/packages/oae-content/lib/internal/dao.content.js @@ -13,12 +13,12 @@ * permissions and limitations under the License. */ -import { +const { isResourceACollabDoc, isResourceACollabSheet, isResourceALink, isResourceAFile -} from 'oae-content/lib/backends/util'; +} = require('oae-content/lib/backends/util'); const util = require('util'); const _ = require('underscore'); diff --git a/packages/oae-content/lib/internal/ethercalc.js b/packages/oae-content/lib/internal/ethercalc.js index 6cee25cf0f..6775989a45 100644 --- a/packages/oae-content/lib/internal/ethercalc.js +++ b/packages/oae-content/lib/internal/ethercalc.js @@ -16,6 +16,7 @@ import EthercalcClient from 'ethercalc-client'; const url = require('url'); + const _ = require('underscore'); const cheerio = require('cheerio'); diff --git a/packages/oae-content/lib/rest.js b/packages/oae-content/lib/rest.js index 6c08d49311..96652e5201 100644 --- a/packages/oae-content/lib/rest.js +++ b/packages/oae-content/lib/rest.js @@ -13,12 +13,12 @@ * permissions and limitations under the License. */ -import { +const { isResourceACollabDoc, isResourceACollabSheet, isResourceAFile, isResourceALink -} from 'oae-content/lib/backends/util'; +} = require('oae-content/lib/backends/util'); const querystring = require('querystring'); const _ = require('underscore'); diff --git a/packages/oae-content/lib/search.js b/packages/oae-content/lib/search.js index 910c1404ab..a05de6f7de 100644 --- a/packages/oae-content/lib/search.js +++ b/packages/oae-content/lib/search.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -import { isResourceACollabDoc, isResourceACollabSheet } from 'oae-content/lib/backends/util'; +const { isResourceACollabDoc, isResourceACollabSheet } = require('oae-content/lib/backends/util'); const fs = require('fs'); const util = require('util'); diff --git a/packages/oae-doc/lib/api.js b/packages/oae-doc/lib/api.js index efea4b5a3c..02c1f03c38 100644 --- a/packages/oae-doc/lib/api.js +++ b/packages/oae-doc/lib/api.js @@ -13,16 +13,17 @@ * permissions and limitations under the License. */ -import fs from 'fs'; -import { logger } from 'oae-logger'; +const fs = require('fs'); -import _ from 'underscore'; -import dox from 'dox'; +const _ = require('underscore'); +const dox = require('dox'); +const { logger } = require('oae-logger'); -import IO from 'oae-util/lib/io'; -import modules from 'oae-util/lib/modules'; -import OaeUtil from 'oae-util/lib/util'; -import { Validator } from 'oae-util/lib/validator'; +const IO = require('oae-util/lib/io'); +const modules = require('oae-util/lib/modules'); +const OaeUtil = require('oae-util/lib/util'); + +const { Validator } = require('oae-util/lib/validator'); const log = logger('oae-doc'); @@ -245,4 +246,4 @@ const getModuleDocumentation = function(moduleId, type, callback) { return callback({ code: 404, msg: 'No documentation for this module was found' }); }; -export { getModules, initializeDocs, getModuleDocumentation }; +module.exports = { getModules, initializeDocs, getModuleDocumentation }; diff --git a/packages/oae-doc/lib/init.js b/packages/oae-doc/lib/init.js index c39881d7c2..fead132922 100644 --- a/packages/oae-doc/lib/init.js +++ b/packages/oae-doc/lib/init.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -import * as DocAPI from './api'; +const DocAPI = require('./api'); module.exports = function(config, callback) { DocAPI.initializeDocs(config.ui, callback); diff --git a/packages/oae-doc/lib/rest.js b/packages/oae-doc/lib/rest.js index b100c06658..040fd2ab8d 100644 --- a/packages/oae-doc/lib/rest.js +++ b/packages/oae-doc/lib/rest.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -import * as OAE from 'oae-util/lib/oae'; -import Swagger from 'oae-util/lib/swagger'; -import * as DocAPI from './api'; +const OAE = require('oae-util/lib/oae'); +const Swagger = require('oae-util/lib/swagger'); +const DocAPI = require('./api'); /** * @REST getDocType diff --git a/packages/oae-logger/lib/api.js b/packages/oae-logger/lib/api.js index f7d3a2faef..9d5c4e4637 100644 --- a/packages/oae-logger/lib/api.js +++ b/packages/oae-logger/lib/api.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -import util from 'util'; -import _ from 'underscore'; -import bunyan from 'bunyan'; +const util = require('util'); +const _ = require('underscore'); +const bunyan = require('bunyan'); // The logger to use when no logger is specified const SYSTEM_LOGGER_NAME = 'system'; @@ -143,4 +143,4 @@ const _wrapErrorFunction = function(loggerName, errorFunction) { return wrapperErrorFunction; }; -export { refreshLogConfiguration, logger }; +module.exports = { refreshLogConfiguration, logger }; diff --git a/packages/oae-logger/lib/init.js b/packages/oae-logger/lib/init.js index 54ba51449d..6b80856a4d 100644 --- a/packages/oae-logger/lib/init.js +++ b/packages/oae-logger/lib/init.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -import { refreshLogConfiguration } from 'oae-logger'; +const { refreshLogConfiguration } = require('oae-logger'); module.exports = function(config, callback) { refreshLogConfiguration(config.log); diff --git a/packages/oae-logger/tests/test-logger.js b/packages/oae-logger/tests/test-logger.js index 9deebfbc65..070dee3e56 100644 --- a/packages/oae-logger/tests/test-logger.js +++ b/packages/oae-logger/tests/test-logger.js @@ -13,13 +13,13 @@ * permissions and limitations under the License. */ -import { strict as assert } from 'assert'; +const util = require('util'); -import util from 'util'; -import { logger } from 'oae-logger'; -import * as RestAPI from 'oae-rest'; -import * as TelemetryAPI from 'oae-telemetry'; -import * as TestsUtil from 'oae-tests/lib/util'; +const assert = require('assert'); +const { logger } = require('oae-logger'); +const RestAPI = require('oae-rest'); +const TelemetryAPI = require('oae-telemetry'); +const TestsUtil = require('oae-tests/lib/util'); describe('Logger', () => { // Rest context that can be used every time we need to make a request as a global admin diff --git a/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js b/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js index 5f21d8aa26..33a0539110 100644 --- a/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js +++ b/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -import { isResourceACollabDoc, isResourceACollabSheet } from 'oae-content/lib/backends/util'; +const { isResourceACollabDoc, isResourceACollabSheet } = require('oae-content/lib/backends/util'); const fs = require('fs'); const Path = require('path'); diff --git a/packages/oae-search/tests/test-tenants-search.js b/packages/oae-search/tests/test-tenants-search.js index 1369c08fd3..c8044d470c 100644 --- a/packages/oae-search/tests/test-tenants-search.js +++ b/packages/oae-search/tests/test-tenants-search.js @@ -43,16 +43,10 @@ describe('Tenants Search', () => { * Test that verifies tenant search is available on the global admin server */ it('verify tenants search works on the global admin server', callback => { - SearchTestsUtil.assertSearchSucceeds( - globalAdminRestContext, - 'tenants', - null, - { q: 'Some querystring' }, - result => { - _assertEmptyTenantsSearchResult(result); - return callback(); - } - ); + SearchTestsUtil.assertSearchSucceeds(globalAdminRestContext, 'tenants', null, { q: 'Some querystring' }, result => { + _assertEmptyTenantsSearchResult(result); + return callback(); + }); }); /** @@ -64,48 +58,47 @@ describe('Tenants Search', () => { const host = TenantsTestUtil.generateTestTenantHost(); // Ensure none of the strings match a tenant yet - SearchTestsUtil.assertSearchSucceeds( - anonymousRestContext, - 'tenants', - null, - { q: alias.toLowerCase() }, - result => { - _assertEmptyTenantsSearchResult(result); - SearchTestsUtil.assertSearchSucceeds( - anonymousRestContext, - 'tenants', - null, - { q: displayName.toLowerCase() }, - result => { - _assertEmptyTenantsSearchResult(result); - SearchTestsUtil.assertSearchSucceeds( - anonymousRestContext, - 'tenants', - null, - { q: host.toLowerCase() }, - result => { - _assertEmptyTenantsSearchResult(result); + SearchTestsUtil.assertSearchSucceeds(anonymousRestContext, 'tenants', null, { q: alias.toLowerCase() }, result => { + _assertEmptyTenantsSearchResult(result); + SearchTestsUtil.assertSearchSucceeds( + anonymousRestContext, + 'tenants', + null, + { q: displayName.toLowerCase() }, + result => { + _assertEmptyTenantsSearchResult(result); + SearchTestsUtil.assertSearchSucceeds( + anonymousRestContext, + 'tenants', + null, + { q: host.toLowerCase() }, + result => { + _assertEmptyTenantsSearchResult(result); - // Create a tenant with the alias, display name and host - TenantsTestUtil.createTenantAndWait( - globalAdminRestContext, - alias, - displayName, - host, - null, - err => { - assert.ok(!err); + // Create a tenant with the alias, display name and host + TenantsTestUtil.createTenantAndWait(globalAdminRestContext, alias, displayName, host, null, err => { + assert.ok(!err); - setTimeout( - SearchTestsUtil.assertSearchSucceeds, - 5000, + setTimeout( + SearchTestsUtil.assertSearchSucceeds, + 5000, + anonymousRestContext, + 'tenants', + null, + { q: alias.toLowerCase() }, + result => { + // Ensure we get the tenant in all searches now + // SearchTestsUtil.assertSearchSucceeds(anonymousRestContext, 'tenants', null, {'q': alias.toLowerCase()}, function(result) { + assert.strictEqual(result.total, 1); + assert.strictEqual(result.results[0].alias, alias); + assert.strictEqual(result.results[0].displayName, displayName); + assert.strictEqual(result.results[0].host, host.toLowerCase()); + SearchTestsUtil.assertSearchSucceeds( anonymousRestContext, 'tenants', null, - { q: alias.toLowerCase() }, + { q: displayName.toLowerCase() }, result => { - // Ensure we get the tenant in all searches now - // SearchTestsUtil.assertSearchSucceeds(anonymousRestContext, 'tenants', null, {'q': alias.toLowerCase()}, function(result) { assert.strictEqual(result.total, 1); assert.strictEqual(result.results[0].alias, alias); assert.strictEqual(result.results[0].displayName, displayName); @@ -114,37 +107,25 @@ describe('Tenants Search', () => { anonymousRestContext, 'tenants', null, - { q: displayName.toLowerCase() }, + { q: host.toLowerCase() }, result => { assert.strictEqual(result.total, 1); assert.strictEqual(result.results[0].alias, alias); assert.strictEqual(result.results[0].displayName, displayName); assert.strictEqual(result.results[0].host, host.toLowerCase()); - SearchTestsUtil.assertSearchSucceeds( - anonymousRestContext, - 'tenants', - null, - { q: host.toLowerCase() }, - result => { - assert.strictEqual(result.total, 1); - assert.strictEqual(result.results[0].alias, alias); - assert.strictEqual(result.results[0].displayName, displayName); - assert.strictEqual(result.results[0].host, host.toLowerCase()); - return callback(); - } - ); + return callback(); } ); } ); } ); - } - ); - } - ); - } - ); + }); + } + ); + } + ); + }); }); /** @@ -166,25 +147,13 @@ describe('Tenants Search', () => { { q: alias }, result => { assert.ok(_.findWhere(result.results, { alias: tenant.alias })); - SearchTestsUtil.assertSearchSucceeds( - anonymousRestContext, - 'tenants', - null, - { q: displayName }, - result => { + SearchTestsUtil.assertSearchSucceeds(anonymousRestContext, 'tenants', null, { q: displayName }, result => { + assert.ok(_.findWhere(result.results, { alias: tenant.alias })); + SearchTestsUtil.assertSearchSucceeds(anonymousRestContext, 'tenants', null, { q: host }, result => { assert.ok(_.findWhere(result.results, { alias: tenant.alias })); - SearchTestsUtil.assertSearchSucceeds( - anonymousRestContext, - 'tenants', - null, - { q: host }, - result => { - assert.ok(_.findWhere(result.results, { alias: tenant.alias })); - return callback(); - } - ); - } - ); + return callback(); + }); + }); } ); }); @@ -197,41 +166,29 @@ describe('Tenants Search', () => { it('verify tenant updates are persisted and search for disabled tenants', callback => { TenantsTestUtil.generateTestTenants(globalAdminRestContext, 1, tenant => { // Ensure the tenant can be found in search - SearchTestsUtil.assertSearchSucceeds( - anonymousRestContext, - 'tenants', - null, - { q: tenant.alias }, - result => { - assert.ok(_.findWhere(result.results, { alias: tenant.alias })); + SearchTestsUtil.assertSearchSucceeds(anonymousRestContext, 'tenants', null, { q: tenant.alias }, result => { + assert.ok(_.findWhere(result.results, { alias: tenant.alias })); + + // Stop the tenant and ensure it no longer appears + TenantsTestUtil.stopTenantAndWait(globalAdminRestContext, tenant.alias, () => { + SearchTestsUtil.assertSearchSucceeds(anonymousRestContext, 'tenants', null, { q: tenant.alias }, result => { + _assertEmptyTenantsSearchResult(result); - // Stop the tenant and ensure it no longer appears - TenantsTestUtil.stopTenantAndWait(globalAdminRestContext, tenant.alias, () => { + // Search while enabling disabled tenants and ensure it appears again SearchTestsUtil.assertSearchSucceeds( anonymousRestContext, 'tenants', null, - { q: tenant.alias }, + { q: tenant.alias, disabled: true }, result => { - _assertEmptyTenantsSearchResult(result); + assert.ok(_.findWhere(result.results, { alias: tenant.alias })); - // Search while enabling disabled tenants and ensure it appears again - SearchTestsUtil.assertSearchSucceeds( - anonymousRestContext, - 'tenants', - null, - { q: tenant.alias, disabled: true }, - result => { - assert.ok(_.findWhere(result.results, { alias: tenant.alias })); - - return callback(); - } - ); + return callback(); } ); }); - } - ); + }); + }); }); }); @@ -240,74 +197,56 @@ describe('Tenants Search', () => { */ it('verify tenant search paging', callback => { // Get the first 3 tenants in search - SearchTestsUtil.assertSearchSucceeds( - anonymousRestContext, - 'tenants', - null, - { start: 0, limit: 3 }, - result => { - _assertTenantsSearchResult(result); - assert.strictEqual(result.results.length, 3); - const tenants = result.results; + SearchTestsUtil.assertSearchSucceeds(anonymousRestContext, 'tenants', null, { start: 0, limit: 3 }, result => { + _assertTenantsSearchResult(result); + assert.strictEqual(result.results.length, 3); + const tenants = result.results; - // Get just the first, second and third and ensure you get just the one tenant - SearchTestsUtil.assertSearchSucceeds( - anonymousRestContext, - 'tenants', - null, - { start: 0, limit: 1 }, - result => { - _assertTenantsSearchResult(result); - assert.deepStrictEqual(result.results, tenants.slice(0, 1)); + // Get just the first, second and third and ensure you get just the one tenant + SearchTestsUtil.assertSearchSucceeds(anonymousRestContext, 'tenants', null, { start: 0, limit: 1 }, result => { + _assertTenantsSearchResult(result); + assert.deepStrictEqual(result.results, tenants.slice(0, 1)); - SearchTestsUtil.assertSearchSucceeds( - anonymousRestContext, - 'tenants', - null, - { start: 1, limit: 1 }, - result => { - _assertTenantsSearchResult(result); - assert.deepStrictEqual(result.results, tenants.slice(1, 2)); - SearchTestsUtil.assertSearchSucceeds( - anonymousRestContext, - 'tenants', - null, - { start: 2, limit: 1 }, - result => { - _assertTenantsSearchResult(result); - assert.deepStrictEqual(result.results, tenants.slice(2, 3)); + SearchTestsUtil.assertSearchSucceeds(anonymousRestContext, 'tenants', null, { start: 1, limit: 1 }, result => { + _assertTenantsSearchResult(result); + assert.deepStrictEqual(result.results, tenants.slice(1, 2)); + SearchTestsUtil.assertSearchSucceeds( + anonymousRestContext, + 'tenants', + null, + { start: 2, limit: 1 }, + result => { + _assertTenantsSearchResult(result); + assert.deepStrictEqual(result.results, tenants.slice(2, 3)); - // Get 2 at a time and ensure you get the two expected - SearchTestsUtil.assertSearchSucceeds( - anonymousRestContext, - 'tenants', - null, - { start: 0, limit: 2 }, - result => { - _assertTenantsSearchResult(result); - assert.deepStrictEqual(result.results, tenants.slice(0, 2)); - SearchTestsUtil.assertSearchSucceeds( - anonymousRestContext, - 'tenants', - null, - { start: 1, limit: 2 }, - result => { - _assertTenantsSearchResult(result); - assert.deepStrictEqual(result.results, tenants.slice(1, 3)); + // Get 2 at a time and ensure you get the two expected + SearchTestsUtil.assertSearchSucceeds( + anonymousRestContext, + 'tenants', + null, + { start: 0, limit: 2 }, + result => { + _assertTenantsSearchResult(result); + assert.deepStrictEqual(result.results, tenants.slice(0, 2)); + SearchTestsUtil.assertSearchSucceeds( + anonymousRestContext, + 'tenants', + null, + { start: 1, limit: 2 }, + result => { + _assertTenantsSearchResult(result); + assert.deepStrictEqual(result.results, tenants.slice(1, 3)); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + return callback(); + } + ); + } + ); + } + ); + }); + }); + }); }); }); diff --git a/packages/oae-tenants/lib/api.js b/packages/oae-tenants/lib/api.js index 4fb2af1c32..1fc2ec4510 100644 --- a/packages/oae-tenants/lib/api.js +++ b/packages/oae-tenants/lib/api.js @@ -13,6 +13,8 @@ * permissions and limitations under the License. */ +const { logger } = require('oae-logger'); + const util = require('util'); const _ = require('underscore'); const async = require('async'); @@ -20,7 +22,6 @@ const async = require('async'); const Cassandra = require('oae-util/lib/cassandra'); const ConfigAPI = require('oae-config'); const EmitterAPI = require('oae-emitter'); -const log = require('oae-logger').logger('oae-tenants'); const OAE = require('oae-util/lib/oae'); const OaeUtil = require('oae-util/lib/util'); const Pubsub = require('oae-util/lib/pubsub'); @@ -31,6 +32,7 @@ const TenantEmailDomainIndex = require('./internal/emailDomainIndex'); const TenantIndex = require('./internal/tenantIndex'); const TenantNetworksDAO = require('./internal/dao.networks'); const TenantsUtil = require('./util'); +const log = logger('oae-tenants'); // Caches the server configuration as specified in the config.js file let serverConfig = null; @@ -119,14 +121,9 @@ const init = function(_serverConfig, callback) { // This middleware adds the tenant to each request on the user tenant server OAE.tenantServer.use((req, res, next) => { if (serverConfig.shibbolethSPHost && req.headers.host === serverConfig.shibbolethSPHost) { - req.tenant = new Tenant( - 'shib-sp', - 'Shibboleth SP hardcoded host', - serverConfig.shibbolethSPHost, - { - active: true - } - ); + req.tenant = new Tenant('shib-sp', 'Shibboleth SP hardcoded host', serverConfig.shibbolethSPHost, { + active: true + }); } else { req.tenant = getTenantByHost(req.headers.host); } @@ -358,12 +355,9 @@ const _cacheTenants = function(callback) { tenantEmailDomainIndex = new TenantEmailDomainIndex(); // Create a dummy tenant object that can serve as the global admin tenant object - globalTenant = new Tenant( - serverConfig.globalAdminAlias, - 'Global admin server', - serverConfig.globalAdminHost, - { isGlobalAdminServer: true } - ); + globalTenant = new Tenant(serverConfig.globalAdminAlias, 'Global admin server', serverConfig.globalAdminHost, { + isGlobalAdminServer: true + }); // Cache it as part of the available tenants tenants[globalTenant.alias] = globalTenant; @@ -469,12 +463,7 @@ const _updateCachedTenant = function(tenantAlias, callback) { // Synchronize the cache of all tenants that are private and disabled so we know which ones // cannot be interacted with - if ( - tenant.isGlobalAdminServer || - !tenant.active || - tenant.deleted || - TenantsUtil.isPrivate(tenant.alias) - ) { + if (tenant.isGlobalAdminServer || !tenant.active || tenant.deleted || TenantsUtil.isPrivate(tenant.alias)) { tenantsNotInteractable[tenant.alias] = tenant; } else { delete tenantsNotInteractable[tenant.alias]; @@ -552,12 +541,8 @@ const _createTenant = function(alias, displayName, host, opts, callback) { const validator = new Validator(); validator.check(alias, { code: 400, msg: 'Missing alias' }).notEmpty(); - validator - .check(alias, { code: 400, msg: 'The tenant alias should not contain a space' }) - .notContains(' '); - validator - .check(alias, { code: 400, msg: 'The tenant alias should not contain a colon' }) - .notContains(':'); + validator.check(alias, { code: 400, msg: 'The tenant alias should not contain a space' }).notContains(' '); + validator.check(alias, { code: 400, msg: 'The tenant alias should not contain a colon' }).notContains(':'); validator.check(displayName, { code: 400, msg: 'Missing tenant displayName' }).notEmpty(); validator.check(host, { code: 400, msg: 'Missing tenant host' }).notEmpty(); validator.check(host, { code: 400, msg: 'Invalid hostname' }).isHost(); @@ -570,9 +555,7 @@ const _createTenant = function(alias, displayName, host, opts, callback) { host = host.toLowerCase(); // Ensure there are no conflicts - validator - .check(host, { code: 400, msg: 'This hostname is reserved' }) - .not(serverConfig.shibbolethSPHost); + validator.check(host, { code: 400, msg: 'This hostname is reserved' }).not(serverConfig.shibbolethSPHost); validator .check(getTenant(alias), { code: 400, @@ -794,10 +777,7 @@ const disableTenants = function(ctx, aliases, disabled, callback) { validator .check(getTenant(alias), { code: 404, - msg: util.format( - 'Tenant with alias "%s" does not exist and cannot be enabled or disabled', - alias - ) + msg: util.format('Tenant with alias "%s" does not exist and cannot be enabled or disabled', alias) }) .notNull(); }); diff --git a/packages/oae-tests/runner/before-tests.js b/packages/oae-tests/runner/before-tests.js index cd4e7a3c2c..42393689fe 100644 --- a/packages/oae-tests/runner/before-tests.js +++ b/packages/oae-tests/runner/before-tests.js @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -import TestsUtil from 'oae-tests/lib/util'; -import { logger } from 'oae-logger'; +const TestsUtil = require('oae-tests/lib/util'); +const { logger } = require('oae-logger'); const log = logger('before-tests'); diff --git a/packages/oae-util/lib/oae.js b/packages/oae-util/lib/oae.js index dce09f2b5b..a86a0f6036 100644 --- a/packages/oae-util/lib/oae.js +++ b/packages/oae-util/lib/oae.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -import { logger } from 'oae-logger'; +const { logger } = require('oae-logger'); const log = logger(); const Modules = require('./modules'); @@ -74,9 +74,13 @@ const init = function(config, callback) { // Start up the global and tenant servers globalAdminServer = Server.setupServer(config.servers.globalAdminPort, config); + module.exports.globalAdminServer = globalAdminServer; tenantServer = Server.setupServer(config.servers.tenantPort, config); + module.exports.tenantServer = tenantServer; tenantRouter = Server.setupRouter(tenantServer); + module.exports.tenantRouter = tenantRouter; globalAdminRouter = Server.setupRouter(globalAdminServer); + module.exports.globalAdminRouter = globalAdminRouter; // Initialize the modules and their CFs, as well as registering the Rest endpoints Modules.bootstrapModules(config, err => { @@ -111,4 +115,4 @@ const registerPreShutdownHandler = function(name, maxTimeMillis, handler) { Shutdown.registerPreShutdownHandler(name, maxTimeMillis, handler); }; -export { globalAdminServer, tenantServer, tenantRouter, globalAdminRouter, init, registerPreShutdownHandler }; +module.exports = { globalAdminServer, tenantServer, tenantRouter, globalAdminRouter, init, registerPreShutdownHandler }; diff --git a/packages/oae-version/lib/api.js b/packages/oae-version/lib/api.js index 1d541b7fe6..c67886cb5c 100644 --- a/packages/oae-version/lib/api.js +++ b/packages/oae-version/lib/api.js @@ -14,11 +14,11 @@ * permissions and limitations under the License. */ -import path from 'path'; -import { Map } from 'immutable'; -import git from 'nodegit'; +const path = require('path'); +const { Map } = require('immutable'); +const git = require('nodegit'); -import _ from 'underscore'; +const _ = require('underscore'); // A variable that will hold the path to the UI directory const hilaryDirectory = path.resolve(__dirname, '..', '..', '..'); @@ -78,4 +78,4 @@ const getVersion = async function(repoPath = hilaryDirectory, repoInformation = return repoInformation; }; -export { getVersion, getVersionCB }; +module.exports = { getVersion, getVersionCB }; diff --git a/packages/oae-version/lib/rest.js b/packages/oae-version/lib/rest.js index da122b4520..58bed8ac6b 100644 --- a/packages/oae-version/lib/rest.js +++ b/packages/oae-version/lib/rest.js @@ -13,8 +13,7 @@ * permissions and limitations under the License. */ -import * as VersionAPI from './api'; - +const VersionAPI = require('./api'); const OAE = require('oae-util/lib/oae'); const _getVersion = async function(req, res) { diff --git a/packages/oae-version/tests/test-version.js b/packages/oae-version/tests/test-version.js index 20c57471b0..6492fac6de 100644 --- a/packages/oae-version/tests/test-version.js +++ b/packages/oae-version/tests/test-version.js @@ -13,13 +13,13 @@ * permissions and limitations under the License. */ -import assert from 'assert'; -import { fromJS } from 'immutable'; +const _ = require('underscore'); -import _ from 'underscore'; +const assert = require('assert'); +const { fromJS } = require('immutable'); -import * as RestAPI from 'oae-rest'; -import * as TestsUtil from 'oae-tests'; +const RestAPI = require('oae-rest'); +const TestsUtil = require('oae-tests'); describe('Git information', function() { /** diff --git a/process.json b/process.json index db5a370dd0..6d36003ff8 100644 --- a/process.json +++ b/process.json @@ -2,7 +2,7 @@ "apps": [ { "name": "Hilary", - "script": "index.js", + "script": "app.js", "cwd": "/opt/current", "watch": true, "node_args": "-r esm", From fccf8aee7a8d658783cc407dd0e9f3fe93800e4d Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Mon, 8 Apr 2019 22:14:04 +0100 Subject: [PATCH 15/21] refactor: move to es6 modules --- .../create-explicit-shib-config.js | 2 +- packages/oae-activity/config/activity.js | 4 +- .../emailTemplates/mail.shared.js | 32 +- packages/oae-activity/lib/activity.js | 51 +- packages/oae-activity/lib/api.js | 58 +- packages/oae-activity/lib/constants.js | 2 +- packages/oae-activity/lib/init.js | 14 +- .../oae-activity/lib/internal/aggregator.js | 273 +- packages/oae-activity/lib/internal/buckets.js | 44 +- packages/oae-activity/lib/internal/config.js | 35 +- packages/oae-activity/lib/internal/dao.js | 267 +- packages/oae-activity/lib/internal/email.js | 459 +- packages/oae-activity/lib/internal/emitter.js | 6 +- .../lib/internal/notifications.js | 69 +- packages/oae-activity/lib/internal/push.js | 170 +- .../oae-activity/lib/internal/registry.js | 56 +- packages/oae-activity/lib/internal/router.js | 395 +- .../oae-activity/lib/internal/transformer.js | 147 +- packages/oae-activity/lib/migration.js | 6 +- packages/oae-activity/lib/model.js | 48 +- packages/oae-activity/lib/rest.js | 29 +- packages/oae-activity/lib/test/util.js | 94 +- packages/oae-activity/lib/util.js | 19 +- packages/oae-activity/tests/test-activity.js | 44 +- packages/oae-activity/tests/test-email.js | 40 +- .../oae-activity/tests/test-notifications.js | 56 +- packages/oae-activity/tests/test-push.js | 31 +- .../oae-authentication/config/strategies.js | 165 +- .../emailTemplates/reset.shared.js | 9 +- packages/oae-authentication/lib/api.js | 189 +- packages/oae-authentication/lib/constants.js | 2 +- packages/oae-authentication/lib/init.js | 58 +- packages/oae-authentication/lib/migration.js | 6 +- packages/oae-authentication/lib/model.js | 6 +- packages/oae-authentication/lib/rest.js | 40 +- .../lib/strategies/cas/init.js | 59 +- .../lib/strategies/cas/rest.js | 18 +- .../lib/strategies/facebook/init.js | 38 +- .../lib/strategies/facebook/rest.js | 18 +- .../lib/strategies/google/init.js | 145 +- .../lib/strategies/google/rest.js | 22 +- .../lib/strategies/ldap/init.js | 22 +- .../lib/strategies/ldap/rest.js | 15 +- .../lib/strategies/local/init.js | 97 +- .../lib/strategies/local/rest.js | 75 +- .../lib/strategies/oauth/api.js | 42 +- .../lib/strategies/oauth/init.js | 41 +- .../oauth/internal/dao.accesstokens.js | 21 +- .../strategies/oauth/internal/dao.clients.js | 39 +- .../lib/strategies/oauth/internal/dao.js | 9 +- .../lib/strategies/oauth/model.js | 5 +- .../lib/strategies/oauth/rest.js | 108 +- .../lib/strategies/shibboleth/api.js | 29 +- .../lib/strategies/shibboleth/init.js | 76 +- .../lib/strategies/shibboleth/rest.js | 39 +- .../lib/strategies/shibboleth/strategy.js | 69 +- .../lib/strategies/signed/init.js | 10 +- .../lib/strategies/signed/rest.js | 35 +- .../lib/strategies/signed/strategy.js | 106 +- .../lib/strategies/signed/util.js | 40 +- .../lib/strategies/twitter/init.js | 39 +- .../lib/strategies/twitter/rest.js | 18 +- packages/oae-authentication/lib/strategy.js | 5 +- packages/oae-authentication/lib/test/util.js | 29 +- packages/oae-authentication/lib/util.js | 54 +- .../tests/test-auth-local.js | 28 +- .../oae-authentication/tests/test-auth.js | 176 +- .../oae-authentication/tests/test-cookies.js | 171 +- .../tests/test-external-strategies.js | 1278 ++--- .../oae-authentication/tests/test-oauth.js | 579 +-- .../tests/test-shibboleth-migration.js | 67 +- .../oae-authentication/tests/test-signed.js | 377 +- .../oae-authentication/tests/test-util.js | 13 +- packages/oae-authz/lib/activity.js | 26 +- packages/oae-authz/lib/api.js | 46 +- packages/oae-authz/lib/constants.js | 2 +- packages/oae-authz/lib/delete.js | 30 +- packages/oae-authz/lib/init.js | 10 +- packages/oae-authz/lib/internal/graph.js | 17 +- packages/oae-authz/lib/invitations/dao.js | 114 +- packages/oae-authz/lib/invitations/index.js | 16 +- packages/oae-authz/lib/invitations/util.js | 14 +- packages/oae-authz/lib/migration.js | 12 +- packages/oae-authz/lib/model.js | 4 +- packages/oae-authz/lib/permissions.js | 41 +- packages/oae-authz/lib/search.js | 23 +- .../search/schema/resourceMembersSchema.js | 12 +- .../schema/resourceMembershipsSchema.js | 12 +- packages/oae-authz/lib/test/util.js | 104 +- packages/oae-authz/lib/util.js | 11 +- packages/oae-authz/lib/validator.js | 20 +- packages/oae-authz/tests/test-authzgraph.js | 42 +- packages/oae-authz/tests/test-delete.js | 8 +- packages/oae-authz/tests/test-groups.js | 1237 ++--- packages/oae-authz/tests/test-invitations.js | 2540 ++++----- packages/oae-authz/tests/test-permissions.js | 6 +- packages/oae-authz/tests/test-roles.js | 293 +- packages/oae-authz/tests/test-validator.js | 4 +- packages/oae-config/lib/api.js | 81 +- packages/oae-config/lib/fields.js | 16 +- packages/oae-config/lib/init.js | 6 +- packages/oae-config/lib/migration.js | 2 +- packages/oae-config/lib/rest.js | 7 +- packages/oae-config/lib/test/util.js | 17 +- packages/oae-config/tests/test-config.js | 1065 ++-- packages/oae-content/config/content.js | 98 +- packages/oae-content/config/storage.js | 91 +- packages/oae-content/lib/activity.js | 298 +- packages/oae-content/lib/api.js | 72 +- packages/oae-content/lib/backends/amazons3.js | 37 +- .../oae-content/lib/backends/interface.js | 15 +- packages/oae-content/lib/backends/local.js | 33 +- packages/oae-content/lib/backends/remote.js | 18 +- packages/oae-content/lib/backends/test.js | 15 +- packages/oae-content/lib/backends/util.js | 16 +- packages/oae-content/lib/constants.js | 4 +- packages/oae-content/lib/init.js | 26 +- .../oae-content/lib/internal/dao.content.js | 30 +- .../oae-content/lib/internal/dao.ethercalc.js | 10 +- .../oae-content/lib/internal/dao.etherpad.js | 20 +- packages/oae-content/lib/internal/dao.js | 12 +- .../oae-content/lib/internal/dao.previews.js | 89 +- .../oae-content/lib/internal/dao.revisions.js | 48 +- .../oae-content/lib/internal/ethercalc.js | 13 +- packages/oae-content/lib/internal/etherpad.js | 54 +- .../lib/internal/membersLibrary.js | 16 +- packages/oae-content/lib/internal/util.js | 71 +- packages/oae-content/lib/invitations.js | 55 +- packages/oae-content/lib/library.js | 101 +- packages/oae-content/lib/migration.js | 10 +- packages/oae-content/lib/model.js | 14 +- packages/oae-content/lib/previews.js | 25 +- packages/oae-content/lib/rest.js | 18 +- packages/oae-content/lib/search.js | 40 +- .../lib/search/schema/contentBodySchema.js | 12 +- packages/oae-content/lib/test/util.js | 32 +- packages/oae-content/tests/test-activity.js | 3921 ++++++-------- packages/oae-content/tests/test-backends.js | 67 +- packages/oae-content/tests/test-collabdoc.js | 24 +- .../oae-content/tests/test-collabsheet.js | 13 +- packages/oae-content/tests/test-content.js | 47 +- packages/oae-content/tests/test-dao.js | 69 +- .../oae-content/tests/test-library-search.js | 154 +- packages/oae-content/tests/test-library.js | 1206 ++--- packages/oae-content/tests/test-previews.js | 794 ++- packages/oae-content/tests/test-push.js | 279 +- packages/oae-content/tests/test-search.js | 131 +- .../tests/test-tenant-separation.js | 18 +- packages/oae-context/lib/api.js | 8 +- packages/oae-context/tests/test-context.js | 30 +- packages/oae-discussions/config/discussion.js | 44 +- packages/oae-discussions/lib/activity.js | 397 +- .../oae-discussions/lib/api.discussions.js | 446 +- packages/oae-discussions/lib/api.js | 8 +- packages/oae-discussions/lib/constants.js | 4 +- packages/oae-discussions/lib/init.js | 7 +- packages/oae-discussions/lib/internal/dao.js | 37 +- packages/oae-discussions/lib/invitations.js | 57 +- packages/oae-discussions/lib/library.js | 256 +- packages/oae-discussions/lib/migration.js | 5 +- packages/oae-discussions/lib/model.js | 17 +- packages/oae-discussions/lib/rest.js | 120 +- packages/oae-discussions/lib/search.js | 83 +- packages/oae-discussions/lib/test/util.js | 440 +- .../oae-discussions/tests/test-activity.js | 759 ++- .../oae-discussions/tests/test-discussions.js | 27 +- .../tests/test-library-search.js | 10 +- .../oae-discussions/tests/test-library.js | 740 ++- packages/oae-discussions/tests/test-push.js | 235 +- packages/oae-discussions/tests/test-search.js | 12 +- packages/oae-doc/lib/api.js | 25 +- packages/oae-doc/lib/init.js | 6 +- packages/oae-doc/lib/rest.js | 10 +- packages/oae-doc/tests/test-doc.js | 107 +- packages/oae-email/config/email.js | 26 +- packages/oae-email/lib/api.js | 131 +- packages/oae-email/lib/init.js | 6 +- packages/oae-email/lib/test/util.js | 41 +- packages/oae-email/tests/test-email.js | 716 +-- packages/oae-emitter/lib/api.js | 22 +- packages/oae-emitter/tests/test-emitter.js | 7 +- packages/oae-folders/config/folder.js | 44 +- packages/oae-folders/lib/activity.js | 319 +- packages/oae-folders/lib/api.js | 637 +-- packages/oae-folders/lib/authz.js | 19 +- packages/oae-folders/lib/constants.js | 4 +- packages/oae-folders/lib/init.js | 6 +- .../lib/internal/contentLibrary.js | 23 +- packages/oae-folders/lib/internal/dao.js | 109 +- .../lib/internal/foldersLibrary.js | 71 +- packages/oae-folders/lib/invitations.js | 56 +- packages/oae-folders/lib/library.js | 250 +- packages/oae-folders/lib/migration.js | 4 +- packages/oae-folders/lib/model.js | 6 +- packages/oae-folders/lib/previews.js | 56 +- packages/oae-folders/lib/rest.js | 101 +- packages/oae-folders/lib/search.js | 107 +- packages/oae-folders/lib/test/util.js | 578 +-- packages/oae-folders/tests/test-activity.js | 824 ++- packages/oae-folders/tests/test-folders.js | 4561 ++++++++--------- packages/oae-folders/tests/test-messages.js | 1537 +++--- packages/oae-folders/tests/test-push.js | 101 +- packages/oae-folders/tests/test-search.js | 1601 +++--- packages/oae-following/lib/activity.js | 65 +- packages/oae-following/lib/api.js | 93 +- packages/oae-following/lib/authz.js | 9 +- packages/oae-following/lib/constants.js | 2 +- packages/oae-following/lib/init.js | 14 +- packages/oae-following/lib/internal/dao.js | 18 +- packages/oae-following/lib/migration.js | 6 +- packages/oae-following/lib/principals.js | 9 +- packages/oae-following/lib/rest.js | 43 +- packages/oae-following/lib/search.js | 63 +- .../search/schema/resourceFollowersSchema.js | 10 +- .../search/schema/resourceFollowingSchema.js | 10 +- .../lib/search/searches/followers.js | 18 +- .../lib/search/searches/following.js | 18 +- packages/oae-following/lib/test/util.js | 238 +- packages/oae-following/tests/test-activity.js | 767 ++- .../oae-following/tests/test-following.js | 614 +-- .../tests/test-profile-decorator.js | 182 +- packages/oae-following/tests/test-search.js | 344 +- .../config/google-analytics.js | 26 +- .../tests/test-google-analytics.js | 46 +- packages/oae-jitsi/config/meeting.js | 58 +- packages/oae-jitsi/lib/activity.js | 362 +- packages/oae-jitsi/lib/api.js | 11 +- packages/oae-jitsi/lib/api.meetings.js | 424 +- packages/oae-jitsi/lib/constants.js | 10 +- packages/oae-jitsi/lib/init.js | 10 +- packages/oae-jitsi/lib/internal/dao.js | 51 +- packages/oae-jitsi/lib/library.js | 152 +- packages/oae-jitsi/lib/migration.js | 6 +- packages/oae-jitsi/lib/model.js | 8 +- packages/oae-jitsi/lib/rest.js | 64 +- packages/oae-jitsi/lib/search.js | 66 +- packages/oae-jitsi/tests/test-activity.js | 185 +- .../oae-jitsi/tests/test-library-search.js | 10 +- packages/oae-jitsi/tests/test-library.js | 550 +- packages/oae-jitsi/tests/test-meetings.js | 687 +-- packages/oae-jitsi/tests/test-push.js | 34 +- packages/oae-jitsi/tests/test-search.js | 12 +- packages/oae-library/lib/api.authz.js | 19 +- packages/oae-library/lib/api.index.js | 34 +- packages/oae-library/lib/api.js | 8 +- packages/oae-library/lib/api.search.js | 33 +- packages/oae-library/lib/init.js | 4 +- packages/oae-library/lib/internal/registry.js | 18 +- packages/oae-library/lib/migration.js | 6 +- packages/oae-library/lib/test/util.js | 11 +- packages/oae-library/tests/test-index.js | 100 +- packages/oae-logger/lib/api.js | 8 +- packages/oae-logger/lib/init.js | 4 +- packages/oae-logger/tests/test-logger.js | 13 +- packages/oae-lti/lib/api.js | 33 +- packages/oae-lti/lib/init.js | 8 +- packages/oae-lti/lib/internal/dao.js | 75 +- packages/oae-lti/lib/migration.js | 6 +- packages/oae-lti/lib/model.js | 18 +- packages/oae-lti/lib/rest.js | 5 +- packages/oae-lti/tests/test-lti.js | 14 +- packages/oae-mediacore/lib/internal/util.js | 2 +- packages/oae-mediacore/lib/processor.js | 2 +- packages/oae-messagebox/lib/api.js | 152 +- packages/oae-messagebox/lib/constants.js | 2 +- packages/oae-messagebox/lib/init.js | 4 +- packages/oae-messagebox/lib/migration.js | 6 +- packages/oae-messagebox/lib/model.js | 14 +- packages/oae-messagebox/lib/search.js | 36 +- .../search/schema/resourceMessagesSchema.js | 12 +- packages/oae-messagebox/lib/util.js | 34 +- .../oae-messagebox/tests/test-messagebox.js | 753 ++- .../oae-preview-processor/config/config.js | 88 +- .../oae-preview-processor/lib/activity.js | 57 +- packages/oae-preview-processor/lib/api.js | 173 +- .../oae-preview-processor/lib/constants.js | 2 +- packages/oae-preview-processor/lib/filters.js | 10 +- packages/oae-preview-processor/lib/init.js | 16 +- .../oae-preview-processor/lib/interface.js | 5 +- .../lib/internal/puppeteer.js | 18 +- packages/oae-preview-processor/lib/model.js | 139 +- .../lib/processors/collabdoc/collabdoc.js | 32 +- .../lib/processors/file/images.js | 14 +- .../lib/processors/file/office.js | 42 +- .../lib/processors/file/pdf.js | 32 +- .../lib/processors/folder/index.js | 200 +- .../lib/processors/link/default.js | 69 +- .../lib/processors/link/flickr.js | 54 +- .../lib/processors/link/slideshare.js | 26 +- .../lib/processors/link/util.js | 15 +- .../lib/processors/link/vimeo.js | 19 +- .../lib/processors/link/youtube.js | 22 +- packages/oae-preview-processor/lib/rest.js | 8 +- .../oae-preview-processor/lib/test/util.js | 65 +- packages/oae-preview-processor/lib/util.js | 41 +- .../tests/test-filters.js | 91 +- .../tests/test-longurl.js | 30 +- .../tests/test-previews.js | 81 +- packages/oae-principals/config/group.js | 72 +- packages/oae-principals/config/recaptcha.js | 24 +- .../config/termsAndConditions.js | 36 +- packages/oae-principals/config/user.js | 239 +- packages/oae-principals/lib/api.group.js | 26 +- .../lib/api.termsAndConditions.js | 20 +- packages/oae-principals/lib/api.user.js | 199 +- packages/oae-principals/lib/internal/dao.js | 13 +- packages/oae-principals/lib/rest.user.js | 2 +- .../oae-principals/tests/test-activity.js | 28 +- .../oae-principals/tests/test-cropping.js | 549 +- packages/oae-principals/tests/test-dao.js | 86 +- packages/oae-principals/tests/test-delete.js | 33 +- packages/oae-principals/tests/test-emails.js | 1313 ++--- .../oae-principals/tests/test-export-data.js | 769 ++- packages/oae-principals/tests/test-groups.js | 59 +- .../tests/test-members-library.js | 19 +- .../tests/test-members-search.js | 476 +- .../tests/test-memberships-library.js | 34 +- .../oae-principals/tests/test-migrations.js | 194 +- packages/oae-principals/tests/test-push.js | 210 +- packages/oae-principals/tests/test-search.js | 15 +- .../tests/test-terms-and-conditions.js | 546 +- packages/oae-principals/tests/test-users.js | 41 +- packages/oae-principals/tests/test-util.js | 181 +- packages/oae-resource/lib/actions.js | 50 +- packages/oae-resource/lib/activity.js | 12 +- packages/oae-resource/lib/constants.js | 2 +- packages/oae-resource/lib/rest.js | 4 +- packages/oae-search/lib/api.js | 35 +- packages/oae-search/lib/constants.js | 2 +- packages/oae-search/lib/init.js | 18 +- .../oae-search/lib/internal/elasticsearch.js | 23 +- packages/oae-search/lib/model.js | 2 +- packages/oae-search/lib/rest.js | 15 +- packages/oae-search/lib/searches/deleted.js | 18 +- packages/oae-search/lib/searches/email.js | 24 +- packages/oae-search/lib/searches/general.js | 18 +- packages/oae-search/lib/test/util.js | 27 +- packages/oae-search/lib/util.js | 42 +- .../oae-search/tests/test-elasticsearch.js | 186 +- .../oae-search/tests/test-email-search.js | 615 ++- .../oae-search/tests/test-general-search.js | 37 +- packages/oae-search/tests/test-search-api.js | 122 +- packages/oae-search/tests/test-search-util.js | 42 +- .../oae-search/tests/test-tenants-search.js | 11 +- packages/oae-telemetry/lib/api.js | 71 +- packages/oae-telemetry/lib/init.js | 6 +- .../oae-telemetry/lib/publishers/circonus.js | 18 +- .../oae-telemetry/lib/publishers/console.js | 9 +- packages/oae-telemetry/lib/rest.js | 25 +- .../oae-telemetry/tests/test-telemetry.js | 23 +- packages/oae-tenants/config/config.js | 34 +- packages/oae-tenants/config/tenant.js | 399 +- packages/oae-tenants/lib/api.js | 67 +- packages/oae-tenants/lib/api.networks.js | 37 +- packages/oae-tenants/lib/init.js | 6 +- .../oae-tenants/lib/internal/dao.networks.js | 74 +- .../lib/internal/emailDomainIndex.js | 47 +- .../oae-tenants/lib/internal/tenantIndex.js | 6 +- packages/oae-tenants/lib/migration.js | 6 +- packages/oae-tenants/lib/model.js | 5 +- packages/oae-tenants/lib/rest.js | 227 +- packages/oae-tenants/lib/test/util.js | 24 +- packages/oae-tenants/lib/util.js | 14 +- .../oae-tenants/tests/test-landing-pages.js | 336 +- .../oae-tenants/tests/test-tenant-networks.js | 732 ++- packages/oae-tenants/tests/test-tenants.js | 1815 +++---- packages/oae-tests/lib/util.js | 79 +- packages/oae-tests/runner/before-tests.js | 8 +- packages/oae-tincanapi/config/tincanapi.js | 33 +- packages/oae-tincanapi/lib/api.js | 41 +- packages/oae-tincanapi/lib/constants.js | 2 +- packages/oae-tincanapi/lib/init.js | 8 +- packages/oae-tincanapi/lib/model.js | 70 +- .../oae-tincanapi/tests/test-tincanapi.js | 397 +- packages/oae-ui/config/skin.js | 21 +- packages/oae-ui/lib/api.js | 106 +- packages/oae-ui/lib/constants.js | 2 +- packages/oae-ui/lib/init.js | 12 +- packages/oae-ui/lib/rest.js | 7 +- packages/oae-ui/lib/test/util.js | 12 +- packages/oae-ui/tests/test-ui.js | 328 +- packages/oae-uservoice/lib/api.js | 2 +- .../oae-uservoice/lib/internal/profile.js | 2 +- packages/oae-util/lib/modules.js | 76 +- packages/oae-util/lib/redis.js | 10 +- packages/oae-version/lib/api.js | 11 +- packages/oae-version/lib/init.js | 2 +- packages/oae-version/lib/rest.js | 4 +- packages/oae-version/tests/test-version.js | 12 +- 389 files changed, 23743 insertions(+), 32795 deletions(-) diff --git a/etc/migration/12.5-to-12.6/create-explicit-shib-config.js b/etc/migration/12.5-to-12.6/create-explicit-shib-config.js index ffff2c7841..a8ecb895a1 100644 --- a/etc/migration/12.5-to-12.6/create-explicit-shib-config.js +++ b/etc/migration/12.5-to-12.6/create-explicit-shib-config.js @@ -70,7 +70,7 @@ const configKey = 'oae-authentication/shibboleth/externalIdAttributes'; const currentDefault = 'eppn persistent-id targeted-id'; function _filterTenants(tenants, callback) { - const AuthenticationConfig = ConfigAPI.config('oae-authentication'); + const AuthenticationConfig = ConfigAPI.setUpConfig('oae-authentication'); const tenantsWithShibEnabled = []; // We only want the tenancies with Shibboleth enabled... diff --git a/packages/oae-activity/config/activity.js b/packages/oae-activity/config/activity.js index 74064255d0..1715b8f426 100644 --- a/packages/oae-activity/config/activity.js +++ b/packages/oae-activity/config/activity.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const Fields = require('oae-config/lib/fields'); +import { Bool } from 'oae-config/lib/fields'; module.exports = { title: 'OAE Activity Module', @@ -21,7 +21,7 @@ module.exports = { name: 'Activity Configuration', description: 'Core Configuration', elements: { - enabled: new Fields.Bool( + enabled: new Bool( 'Activity Posting Enabled', 'When disabled, no actions originating from the tenant will trigger an activity', true, diff --git a/packages/oae-activity/emailTemplates/mail.shared.js b/packages/oae-activity/emailTemplates/mail.shared.js index d421c33954..690d30a518 100644 --- a/packages/oae-activity/emailTemplates/mail.shared.js +++ b/packages/oae-activity/emailTemplates/mail.shared.js @@ -13,11 +13,11 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); +import * as AuthzUtil from 'oae-authz/lib/util'; -const { ActivityConstants } = require('oae-activity/lib/constants'); +import { ActivityConstants } from 'oae-activity/lib/constants'; /** * Determine if the given string is an email @@ -53,10 +53,7 @@ const getEmailSubject = (util, recipient, activities) => { // a little bit differently because we have to legitimize our email quicker (i.e., in the // subject) if (_.size(activities) === 1) { - return util.i18n.translate( - activities[0].summary.i18nKey, - activities[0].summary.i18nArguments - ); + return util.i18n.translate(activities[0].summary.i18nKey, activities[0].summary.i18nArguments); } const actors = _getAllEntities(activities, 'actor'); @@ -65,27 +62,27 @@ const getEmailSubject = (util, recipient, activities) => { actor1DisplayName: actors[0].displayName }); } + if (_.size(actors) === 2) { return util.i18n.translate('__MSG__ACTIVITY_EMAIL_SUBJECT_INVITE_ACTOR_2__', { actor1DisplayName: actors[0].displayName, actor2DisplayName: actors[1].displayName }); } + return util.i18n.translate('__MSG__ACTIVITY_EMAIL_SUBJECT_INVITE_ACTOR_3+__', { actor1DisplayName: actors[0].displayName, numActorsMinus1: actors.length - 1 }); } + // If the user already has an account, we can generalize a bit on what has happened based // on their email preference and number of activities const { emailPreference } = recipient; let message = util.i18n.translate('__MSG__RECENT_ACTIVITY__'); if (emailPreference === 'immediate') { if (activities.length === 1) { - message = util.i18n.translate( - activities[0].summary.i18nKey, - activities[0].summary.i18nArguments - ); + message = util.i18n.translate(activities[0].summary.i18nKey, activities[0].summary.i18nArguments); } else { message = util.i18n.translate('__MSG__ACTIVITY_EMAIL_SUBJECT_MULTIPLE__'); } @@ -124,17 +121,20 @@ const getEmailSummary = function(util, recipient, activities, baseUrl) { actor1DisplayName: actors[0].displayName }); } + if (_.size(actors) === 2) { return util.i18n.translate('__MSG__ACTIVITY_EMAIL_SUBJECT_INVITE_ACTOR_2__', { actor1DisplayName: actors[0].displayName, actor2DisplayName: actors[1].displayName }); } + return util.i18n.translate('__MSG__ACTIVITY_EMAIL_SUBJECT_INVITE_ACTOR_3+__', { actor1DisplayName: actors[0].displayName, numActorsMinus1: actors.length - 1 }); } + const { emailPreference } = recipient; if (emailPreference === 'immediate') { // Determine if there was a single or multiple actors @@ -144,6 +144,7 @@ const getEmailSummary = function(util, recipient, activities, baseUrl) { if (actor !== null && actor['oae:id'] !== activity.originalActivity.actor['oae:id']) { isSingleActor = false; } + actor = activity.originalActivity.actor; }); @@ -165,11 +166,14 @@ const getEmailSummary = function(util, recipient, activities, baseUrl) { return util.url.ensureAbsoluteLinks(summary, baseUrl); } + return '__MSG__ACTIVITY_EMAIL_SUMMARY_IMMEDIATE_MULTIPLE_ACTORS__'; } + if (emailPreference === 'daily') { return '__MSG__ACTIVITY_EMAIL_SUMMARY_DAILY__'; } + if (emailPreference === 'weekly') { return '__MSG__ACTIVITY_EMAIL_SUMMARY_WEEKLY__'; } @@ -209,8 +213,4 @@ const _getAllEntities = function(activities, entityType) { return entities; }; -module.exports = { - isEmail, - getEmailSubject, - getEmailSummary -}; +export { isEmail, getEmailSubject, getEmailSummary }; diff --git a/packages/oae-activity/lib/activity.js b/packages/oae-activity/lib/activity.js index d578c42649..2b1f1f049e 100644 --- a/packages/oae-activity/lib/activity.js +++ b/packages/oae-activity/lib/activity.js @@ -13,17 +13,17 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const AuthzAPI = require('oae-authz'); -const AuthzUtil = require('oae-authz/lib/util'); -const ContentAPI = require('oae-content'); -const DiscussionsAPI = require('oae-discussions'); -const FoldersAPI = require('oae-folders'); -const Signature = require('oae-util/lib/signature'); -const MeetingsAPI = require('oae-jitsi'); +import * as AuthzAPI from 'oae-authz'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ContentAPI from 'oae-content'; +import * as DiscussionsAPI from 'oae-discussions'; +import * as FoldersAPI from 'oae-folders'; +import * as Signature from 'oae-util/lib/signature'; +import * as MeetingsAPI from 'oae-jitsi'; -const ActivityAPI = require('./api'); +import * as ActivityAPI from './api'; ActivityAPI.registerActivityStreamType('activity', { transient: false, @@ -36,36 +36,43 @@ ActivityAPI.registerActivityStreamType('activity', { // User streams } + if (AuthzUtil.isUserId(resourceId)) { return _authorizeUserActivityStream(ctx, resourceId, token, callback); // Group streams } + if (AuthzUtil.isGroupId(resourceId)) { return _authorizeGroupActivityStream(ctx, resourceId, token, callback); // Content streams } + if (resource.resourceType === 'c') { return _authorizeContentActivityStream(ctx, resourceId, token, callback); // Discussion streams } + if (resource.resourceType === 'd') { return _authorizeDiscussionActivityStream(ctx, resourceId, token, callback); // Folder streams } + if (resource.resourceType === 'f') { return _authorizeFolderActivityStream(ctx, resourceId, token, callback); // Jitsi streams } + if (resource.resourceType === 'm') { return _authorizeJitsiActivityStream(ctx, resourceId, token, callback); // Unknown type of resource } + return callback({ code: 404, msg: 'Unknown type of resource' }); } }); @@ -80,26 +87,31 @@ ActivityAPI.registerActivityStreamType('message', { // Content streams } + if (resourceId[0] === 'c') { return _authorizeContentActivityStream(ctx, resourceId, token, callback); // Discussion streams } + if (resourceId[0] === 'd') { return _authorizeDiscussionActivityStream(ctx, resourceId, token, callback); // Folder streams } + if (resourceId[0] === 'f') { return _authorizeFolderActivityStream(ctx, resourceId, token, callback); // Meeting streams } + if (resourceId[0] === 'm') { return _authorizeJitsiActivityStream(ctx, resourceId, token, callback); // Unknown type of resource } + return callback({ code: 404, msg: 'Unknown type of resource' }); } }); @@ -122,6 +134,7 @@ ActivityAPI.registerActivityStreamType('notification', { msg: 'Only authenticated users can retrieve a notification stream' }); } + if (ctx.user().id !== resourceId) { return callback({ code: 401, msg: 'You can only request your own notification stream' }); } @@ -147,6 +160,7 @@ const _authorizeUserActivityStream = function(ctx, userId, token, callback) { msg: "Only authenticated users can retrieve a user's activity stream" }); } + if (ctx.user().id !== userId) { return callback({ code: 401, msg: 'You can only request your own notification stream' }); } @@ -164,6 +178,7 @@ const _authorizeGroupActivityStream = function(ctx, groupId, token, callback) { if (!ctx.user()) { return callback({ code: 401, msg: 'Must be a member of a group to see its activity stream' }); } + if (_.isObject(token)) { if (!Signature.verifyExpiringResourceSignature(ctx, groupId, token.expires, token.signature)) { return callback({ code: 401, msg: 'Invalid signature' }); @@ -171,10 +186,12 @@ const _authorizeGroupActivityStream = function(ctx, groupId, token, callback) { return callback(); } + AuthzAPI.hasAnyRole(ctx.user().id, groupId, (err, hasAnyRole) => { if (err) { return callback(err); } + if (!hasAnyRole) { return callback({ code: 401, msg: 'Must be a member of a group to see its activity stream' }); } @@ -191,14 +208,13 @@ const _authorizeGroupActivityStream = function(ctx, groupId, token, callback) { */ const _authorizeContentActivityStream = function(ctx, contentId, token, callback) { if (_.isObject(token)) { - if ( - !Signature.verifyExpiringResourceSignature(ctx, contentId, token.expires, token.signature) - ) { + if (!Signature.verifyExpiringResourceSignature(ctx, contentId, token.expires, token.signature)) { return callback({ code: 401, msg: 'Invalid signature' }); } return callback(); } + ContentAPI.getContent(ctx, contentId, err => { if (err) { return callback(err); @@ -216,14 +232,13 @@ const _authorizeContentActivityStream = function(ctx, contentId, token, callback */ const _authorizeDiscussionActivityStream = function(ctx, discussionId, token, callback) { if (_.isObject(token)) { - if ( - !Signature.verifyExpiringResourceSignature(ctx, discussionId, token.expires, token.signature) - ) { + if (!Signature.verifyExpiringResourceSignature(ctx, discussionId, token.expires, token.signature)) { return callback({ code: 401, msg: 'Invalid signature' }); } return callback(); } + DiscussionsAPI.Discussions.getDiscussion(ctx, discussionId, err => { if (err) { return callback(err); @@ -247,6 +262,7 @@ const _authorizeFolderActivityStream = function(ctx, folderId, token, callback) return callback(); } + FoldersAPI.getFolder(ctx, folderId, err => { if (err) { return callback(err); @@ -264,14 +280,13 @@ const _authorizeFolderActivityStream = function(ctx, folderId, token, callback) */ const _authorizeJitsiActivityStream = function(ctx, meetingId, token, callback) { if (_.isObject(token)) { - if ( - !Signature.verifyExpiringResourceSignature(ctx, meetingId, token.expires, token.signature) - ) { + if (!Signature.verifyExpiringResourceSignature(ctx, meetingId, token.expires, token.signature)) { return callback({ code: 401, msg: 'Invalid signature' }); } return callback(); } + MeetingsAPI.getMeeting(ctx, meetingId, err => { if (err) { return callback(err); diff --git a/packages/oae-activity/lib/api.js b/packages/oae-activity/lib/api.js index 5d24bbe27e..8b5910520f 100644 --- a/packages/oae-activity/lib/api.js +++ b/packages/oae-activity/lib/api.js @@ -13,30 +13,33 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); - -const AuthzUtil = require('oae-authz/lib/util'); -const LibraryAuthz = require('oae-library/lib/api.authz'); -const log = require('oae-logger').logger('oae-activity-api'); -const OAE = require('oae-util/lib/oae'); -const OaeUtil = require('oae-util/lib/util'); -const PrincipalsUtil = require('oae-principals/lib/util'); -const Redis = require('oae-util/lib/redis'); -const TaskQueue = require('oae-util/lib/taskqueue'); -const { Validator } = require('oae-authz/lib/validator'); - -const ActivityConfig = require('oae-config').config('oae-activity'); -const { ActivityConstants } = require('oae-activity/lib/constants'); -const { ActivityStream } = require('oae-activity/lib/model'); -const ActivityEmail = require('./internal/email'); -const ActivityEmitter = require('./internal/emitter'); -const ActivityNotifications = require('./internal/notifications'); -const ActivityRegistry = require('./internal/registry'); -const ActivityRouter = require('./internal/router'); -const ActivitySystemConfig = require('./internal/config'); -const ActivityTransformer = require('./internal/transformer'); -const ActivityDAO = require('./internal/dao'); -const ActivityAggregator = require('./internal/aggregator'); +import _ from 'underscore'; + +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as LibraryAuthz from 'oae-library/lib/api.authz'; +import { logger } from 'oae-logger'; +import * as OAE from 'oae-util/lib/oae'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; +import * as Redis from 'oae-util/lib/redis'; +import * as TaskQueue from 'oae-util/lib/taskqueue'; +import { Validator } from 'oae-authz/lib/validator'; + +import { setUpConfig } from 'oae-config'; +import { ActivityConstants } from 'oae-activity/lib/constants'; +import { ActivityStream } from 'oae-activity/lib/model'; +import ActivityEmitter from './internal/emitter'; +import * as ActivityEmail from './internal/email'; +import * as ActivityNotifications from './internal/notifications'; +import * as ActivityRegistry from './internal/registry'; +import * as ActivityRouter from './internal/router'; +import * as ActivitySystemConfig from './internal/config'; +import * as ActivityTransformer from './internal/transformer'; +import * as ActivityDAO from './internal/dao'; +import * as ActivityAggregator from './internal/aggregator'; + +const log = logger('oae-activity-api'); +const ActivityConfig = setUpConfig('oae-activity'); // Keeps track of whether or not the activity processing handler has been bound to the task queue let boundWorker = false; @@ -114,10 +117,12 @@ const refreshConfiguration = function(config, callback) { callback ); } + if (!config.processActivityJobs && boundWorker) { boundWorker = false; return TaskQueue.unbind(ActivityConstants.mq.TASK_ACTIVITY, callback); } + return callback(); }; @@ -550,6 +555,7 @@ const getActivityStream = function(ctx, principalId, start, limit, transformerTy if (err) { return callback(err); } + if (!hasAccess) { return callback({ code: 401, msg: 'You cannot access this activity stream' }); } @@ -760,7 +766,7 @@ const _getActivityStream = function(ctx, activityStreamId, start, limit, transfo }); }; -module.exports = { +export { refreshConfiguration, registerActivityStreamType, getRegisteredActivityStreamType, @@ -771,5 +777,5 @@ module.exports = { getNotificationStream, markNotificationsRead, postActivity, - emitter: ActivityEmitter + ActivityEmitter as emitter }; diff --git a/packages/oae-activity/lib/constants.js b/packages/oae-activity/lib/constants.js index 9c90f0f9f8..2299d26871 100644 --- a/packages/oae-activity/lib/constants.js +++ b/packages/oae-activity/lib/constants.js @@ -82,4 +82,4 @@ ActivityConstants.streams = { EMAIL: 'email' }; -module.exports = { ActivityConstants }; +export { ActivityConstants }; diff --git a/packages/oae-activity/lib/init.js b/packages/oae-activity/lib/init.js index c631a983ab..75ada831b1 100644 --- a/packages/oae-activity/lib/init.js +++ b/packages/oae-activity/lib/init.js @@ -13,22 +13,22 @@ * permissions and limitations under the License. */ -const ActivityAPI = require('oae-activity'); -const ActivityPush = require('./internal/push'); +import * as ActivityAPI from 'oae-activity'; +import * as ActivityPush from './internal/push'; // Register some of the default streams // eslint-disable-next-line import/no-unassigned-import -require('./activity'); +import * as Activity from './activity'; // Bind the notification event listeners // eslint-disable-next-line import/no-unassigned-import -require('./internal/notifications'); +import * as Notifications from './internal/notifications'; // Bind the email event listeners // eslint-disable-next-line import/no-unassigned-import -require('./internal/email'); +import * as Email from './internal/email'; -module.exports = function(config, callback) { +export function init(config, callback) { ActivityAPI.refreshConfiguration(config.activity, err => { if (err) { return callback(err); @@ -37,4 +37,4 @@ module.exports = function(config, callback) { // Configure the push notifications ActivityPush.init(callback); }); -}; +} diff --git a/packages/oae-activity/lib/internal/aggregator.js b/packages/oae-activity/lib/internal/aggregator.js index 1142f135ab..71747cd6ea 100644 --- a/packages/oae-activity/lib/internal/aggregator.js +++ b/packages/oae-activity/lib/internal/aggregator.js @@ -13,21 +13,25 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); - -const log = require('oae-logger').logger('oae-activity-aggregator'); -const Telemetry = require('oae-telemetry').telemetry('activity'); - -const { Activity } = require('oae-activity/lib/model'); -const { ActivityConstants } = require('oae-activity/lib/constants'); -const { ActivityEntity } = require('oae-activity/lib/model'); -const ActivityUtil = require('oae-activity/lib/util'); -const ActivityRegistry = require('./registry'); -const ActivitySystemConfig = require('./config'); -const ActivityEmitter = require('./emitter'); -const ActivityDAO = require('./dao'); -const ActivityBuckets = require('./buckets'); +import util from 'util'; +import _ from 'underscore'; + +import { logger } from 'oae-logger'; +import { telemetry } from 'oae-telemetry'; + +import { Activity } from 'oae-activity/lib/model'; +import { ActivityConstants } from 'oae-activity/lib/constants'; +import { ActivityEntity } from 'oae-activity/lib/model'; +import * as ActivityUtil from 'oae-activity/lib/util'; +import * as ActivityRegistry from './registry'; +import * as ActivitySystemConfig from './config'; +import ActivityEmitter from './emitter'; +import * as ActivityDAO from './dao'; +import * as ActivityBuckets from './buckets'; + +const log = logger('oae-activity-aggregator'); + +const Telemetry = telemetry('activity'); // Used in an aggregate key to denote that there was no entity provided for an activity. This differs from an empty string in that // an empty string is used when the aggregate does not pivot on that entity. @@ -150,6 +154,7 @@ const _collectBucket = function(bucketNumber, callback) { if (err) { return callback(err); } + if (numToDelete === 0) { // No more to process, so stop and report that we're empty return callback(null, true); @@ -206,52 +211,52 @@ const _collectBucket = function(bucketNumber, callback) { } /*! - * Step #7: - * - * Here we choose which aggregates need to be wrapped up into an activity and delivered to the activity stream. This is - * rather difficult to get right. These are the rules implemented below: - * - * For a given activity: - * - * 1. If a matching multi-aggregate exists for an activity, it will "claim" the activity for that stream and redeliver - * the updated aggregate, while deleting the old version of the activity. If more than one multi-aggregate matches - * the activity, all matching multi-aggregates are redelivered. This is to support the situation where: - * - * Aggregate #1: "Branden followed Simon and Bert" - * Aggregate #2: "Nicolaas and Stuart followed Stephen" - * - * Now, when Branden follows Stephen, both of those "multi-aggregates" will claim this activity, as such: - * - * Aggregate #1': "Branden followed Simon, Bert and Stephen" - * Aggregate #2': "Nicolaas, Stuart and Branden followed Stephen" - * - * 2. For all activities that haven't been claimed, if a single-aggregate exists for an activity, it will "claim" the - * activity for that stream and redeliver the updated aggregate, while deleting the old version of the activity. If - * more than one single-aggregate matches the activity, all matching single-aggregates are redelivered. This is to - * support the situation where: - * - * Aggregate #1: "Branden followed Simon" - * Aggregate #2: "Nicolaas followed Stephen" - * - * Now, when Branden followed Stephen, both of those "single-aggregates" will claim this activity and become "multi- - * aggregates", as such: - * - * Aggregate #1: "Branden followed Simon and Stephen" - * Aggregate #2: "Nicolaas and Branden followed Stephen" - * - * 3. An activity is only delivered for an inactive aggregate if the activity was not "claimed" for the route by an - * active single- or multi-aggregate. This would make sure that we don't redeliver an active aggregate, AND deliver - * a new single-aggregate (e.g., "Branden shared Syllabus with OAE Team") for the same route. - * - * 4. If no active aggregates claim an activity, and there are multiple inactive aggregates (e.g., the activity type has - * multiple "pivot points"), then one single activity is delivered for all of them. This is necessary to ensure that - * the "lastActivityId" is recorded properly for both aggregates, so if either of those inactive aggregates become - * active later (i.e., another activity comes along and matches it), the previous activity can be properly deleted by - * either of the aggregates. - * - * FIXMEMAYBE: https://github.com/oaeproject/Hilary/pull/650#issuecomment-23865585 - * - */ + * Step #7: + * + * Here we choose which aggregates need to be wrapped up into an activity and delivered to the activity stream. This is + * rather difficult to get right. These are the rules implemented below: + * + * For a given activity: + * + * 1. If a matching multi-aggregate exists for an activity, it will "claim" the activity for that stream and redeliver + * the updated aggregate, while deleting the old version of the activity. If more than one multi-aggregate matches + * the activity, all matching multi-aggregates are redelivered. This is to support the situation where: + * + * Aggregate #1: "Branden followed Simon and Bert" + * Aggregate #2: "Nicolaas and Stuart followed Stephen" + * + * Now, when Branden follows Stephen, both of those "multi-aggregates" will claim this activity, as such: + * + * Aggregate #1': "Branden followed Simon, Bert and Stephen" + * Aggregate #2': "Nicolaas, Stuart and Branden followed Stephen" + * + * 2. For all activities that haven't been claimed, if a single-aggregate exists for an activity, it will "claim" the + * activity for that stream and redeliver the updated aggregate, while deleting the old version of the activity. If + * more than one single-aggregate matches the activity, all matching single-aggregates are redelivered. This is to + * support the situation where: + * + * Aggregate #1: "Branden followed Simon" + * Aggregate #2: "Nicolaas followed Stephen" + * + * Now, when Branden followed Stephen, both of those "single-aggregates" will claim this activity and become "multi- + * aggregates", as such: + * + * Aggregate #1: "Branden followed Simon and Stephen" + * Aggregate #2: "Nicolaas and Branden followed Stephen" + * + * 3. An activity is only delivered for an inactive aggregate if the activity was not "claimed" for the route by an + * active single- or multi-aggregate. This would make sure that we don't redeliver an active aggregate, AND deliver + * a new single-aggregate (e.g., "Branden shared Syllabus with OAE Team") for the same route. + * + * 4. If no active aggregates claim an activity, and there are multiple inactive aggregates (e.g., the activity type has + * multiple "pivot points"), then one single activity is delivered for all of them. This is necessary to ensure that + * the "lastActivityId" is recorded properly for both aggregates, so if either of those inactive aggregates become + * active later (i.e., another activity comes along and matches it), the previous activity can be properly deleted by + * either of the aggregates. + * + * FIXMEMAYBE: https://github.com/oaeproject/Hilary/pull/650#issuecomment-23865585 + * + */ // Keeps track of all aggregate keys that should actually be delivered. Not all potential aggregates get delivered // because other aggregates may take priority or they may be duplicates of existing activities @@ -286,13 +291,12 @@ const _collectBucket = function(bucketNumber, callback) { // Mark this to be delivered and assign it an activity id aggregatesToDeliver[aggregateKey] = true; - aggregate[ - ActivityConstants.properties.OAE_ACTIVITY_ID - ] = ActivityDAO.createActivityId(aggregate.published); + aggregate[ActivityConstants.properties.OAE_ACTIVITY_ID] = ActivityDAO.createActivityId( + aggregate.published + ); // Mark these activities for this route as being claimed by an active aggregate - claimedRouteActivities[aggregate.route] = - claimedRouteActivities[aggregate.route] || {}; + claimedRouteActivities[aggregate.route] = claimedRouteActivities[aggregate.route] || {}; _.each(aggregate.activityIds, activityId => { claimedRouteActivities[aggregate.route][activityId] = true; }); @@ -306,8 +310,7 @@ const _collectBucket = function(bucketNumber, callback) { // but NOT with activities already delivered to the feed, it means multiple activities // were launched in quick successesion (content-create for example) that could be aggregated // into one single activity. This increments the number of new activities for this route by 1 - numNewActivitiesByRoute[aggregate.route] = - numNewActivitiesByRoute[aggregate.route] || 0; + numNewActivitiesByRoute[aggregate.route] = numNewActivitiesByRoute[aggregate.route] || 0; numNewActivitiesByRoute[aggregate.route]++; } } @@ -323,20 +326,18 @@ const _collectBucket = function(bucketNumber, callback) { // have already been claimed as multi-aggregates const activityId = aggregate.activityIds[0]; const isClaimed = - claimedRouteActivities[aggregate.route] && - claimedRouteActivities[aggregate.route][activityId]; + claimedRouteActivities[aggregate.route] && claimedRouteActivities[aggregate.route][activityId]; if (!isClaimed && activeAggregates[aggregateKey]) { const status = statusByAggregateKey[aggregateKey]; // Mark this to be delivered and assign it an activity id aggregatesToDeliver[aggregateKey] = true; - aggregate[ - ActivityConstants.properties.OAE_ACTIVITY_ID - ] = ActivityDAO.createActivityId(aggregate.published); + aggregate[ActivityConstants.properties.OAE_ACTIVITY_ID] = ActivityDAO.createActivityId( + aggregate.published + ); // Mark these activities for this route as being claimed by an active aggregate - claimedRouteActivities[aggregate.route] = - claimedRouteActivities[aggregate.route] || {}; + claimedRouteActivities[aggregate.route] = claimedRouteActivities[aggregate.route] || {}; _.each(aggregate.activityIds, activityId => { claimedRouteActivities[aggregate.route][activityId] = true; }); @@ -360,8 +361,7 @@ const _collectBucket = function(bucketNumber, callback) { const activityId = aggregate.activityIds[0]; const isClaimed = - claimedRouteActivities[aggregate.route] && - claimedRouteActivities[aggregate.route][activityId]; + claimedRouteActivities[aggregate.route] && claimedRouteActivities[aggregate.route][activityId]; if (!isClaimed) { // If this route has not received an aggregate, then we deliver the non-active one(s). In the event that // there are multiple non-active aggregates, a duplicate activity will not be fired because we flatten and @@ -375,8 +375,7 @@ const _collectBucket = function(bucketNumber, callback) { // track of whether an activity already incremented the notification count const flattenedActivity = _flattenActivity(aggregate); if (!incrementedForActivities[flattenedActivity]) { - numNewActivitiesByRoute[aggregate.route] = - numNewActivitiesByRoute[aggregate.route] || 0; + numNewActivitiesByRoute[aggregate.route] = numNewActivitiesByRoute[aggregate.route] || 0; numNewActivitiesByRoute[aggregate.route]++; incrementedForActivities[flattenedActivity] = true; } @@ -416,10 +415,10 @@ const _collectBucket = function(bucketNumber, callback) { if (visitedActivities[flattenedActivity]) { // We assign the previous activity id to the aggregate so that we can update the aggregate status to know that // any new activities for this aggregate should replace its existing activity - aggregate[ActivityConstants.properties.OAE_ACTIVITY_ID] = - visitedActivities[flattenedActivity]; + aggregate[ActivityConstants.properties.OAE_ACTIVITY_ID] = visitedActivities[flattenedActivity]; return; } + // This activity is not a duplicate, assign and record a new activityId activityId = ActivityDAO.createActivityId(aggregate.published); aggregate[ActivityConstants.properties.OAE_ACTIVITY_ID] = activityId; @@ -430,8 +429,7 @@ const _collectBucket = function(bucketNumber, callback) { const object = createActivityEntity(_.values(aggregate.objects)); const target = createActivityEntity(_.values(aggregate.targets)); - activityStreamUpdates[aggregate.route] = - activityStreamUpdates[aggregate.route] || {}; + activityStreamUpdates[aggregate.route] = activityStreamUpdates[aggregate.route] || {}; activityStreamUpdates[aggregate.route][activityId] = new Activity( activityType, activityId, @@ -484,8 +482,7 @@ const _collectBucket = function(bucketNumber, callback) { if (!activeAggregates[aggregateKey]) { // This aggregate was not previously active, so mark its creation date at the beginning of the first activity - statusUpdatesByActivityStreamId[aggregate.route][aggregateKey].created = - aggregate.published; + statusUpdatesByActivityStreamId[aggregate.route][aggregateKey].created = aggregate.published; } // Mark the last activity for each aggregate. This ensures that when a new activity gets added to the aggregate, we can @@ -505,28 +502,21 @@ const _collectBucket = function(bucketNumber, callback) { // Fire an event that we have successfully delivered these individual activities const deliveredActivityInfos = {}; _.each(routedActivities, routedActivity => { - const activityStream = ActivityUtil.parseActivityStreamId( - routedActivity.route - ); + const activityStream = ActivityUtil.parseActivityStreamId(routedActivity.route); const { streamType, resourceId } = activityStream; deliveredActivityInfos[resourceId] = deliveredActivityInfos[resourceId] || {}; - deliveredActivityInfos[resourceId][streamType] = deliveredActivityInfos[ - resourceId - ][streamType] || { + deliveredActivityInfos[resourceId][streamType] = deliveredActivityInfos[resourceId][ + streamType + ] || { numNewActivities: numNewActivitiesByRoute[routedActivity.route] || 0, activities: [] }; - deliveredActivityInfos[resourceId][streamType].activities.push( - routedActivity.activity - ); + deliveredActivityInfos[resourceId][streamType].activities.push(routedActivity.activity); }); if (!_.isEmpty(deliveredActivityInfos)) { - ActivityEmitter.emit( - ActivityConstants.events.DELIVERED_ACTIVITIES, - deliveredActivityInfos - ); + ActivityEmitter.emit(ActivityConstants.events.DELIVERED_ACTIVITIES, deliveredActivityInfos); } Telemetry.appendDuration('collection.time', collectionStart); @@ -643,12 +633,7 @@ const createAggregates = function(routedActivities) { // that already match. It helps us deliver an aggregate right away to the route, rather than accidentally delivering // individual activities from within a batch if (!aggregates[aggregateKey]) { - aggregates[aggregateKey] = new ActivityAggregate( - activityType, - route, - activity.verb, - activity.published - ); + aggregates[aggregateKey] = new ActivityAggregate(activityType, route, activity.verb, activity.published); } const aggregate = aggregates[aggregateKey]; @@ -722,6 +707,7 @@ const _createPivotKey = function(entity, pivotSpec) { ); return ''; } + return key; }; @@ -739,9 +725,7 @@ const _createPivotKey = function(entity, pivotSpec) { * @api private */ const _contributesNewEntity = function(aggregate, actorKey, objectKey, targetKey) { - return ( - !aggregate.actors[actorKey] || !aggregate.objects[objectKey] || !aggregate.targets[targetKey] - ); + return !aggregate.actors[actorKey] || !aggregate.objects[objectKey] || !aggregate.targets[targetKey]; }; /** @@ -774,10 +758,12 @@ const createActivityEntity = function(entities) { if (!entities) { return undefined; } + // eslint-disable-next-line unicorn/explicit-length-check if (!entities.length) { return undefined; } + if (entities.length === 1) { return entities[0]; } @@ -890,44 +876,44 @@ const ActivityAggregate = function(activityType, route, verb, published) { that.targets = {}; /*! - * Update the existing (if any) actor in the aggregate with the given actor. If the actor did not exist on the aggregate it will - * be added. - * - * @param {String} actorKey The unique key of the actor object - * @param {Object} actor The actor object to update - */ + * Update the existing (if any) actor in the aggregate with the given actor. If the actor did not exist on the aggregate it will + * be added. + * + * @param {String} actorKey The unique key of the actor object + * @param {Object} actor The actor object to update + */ that.updateActor = function(actorKey, actor) { that.actors[actorKey] = actor; }; /*! - * Update the existing (if any) object in the aggregate with the given object. If the object did not exist on the aggregate it - * will be added. - * - * @param {String} objectKey The unique key of the object object - * @param {Object} object The object object to update - */ + * Update the existing (if any) object in the aggregate with the given object. If the object did not exist on the aggregate it + * will be added. + * + * @param {String} objectKey The unique key of the object object + * @param {Object} object The object object to update + */ that.updateObject = function(objectKey, object) { that.objects[objectKey] = object; }; /*! - * Update the existing (if any) target in the aggregate with the given target. If the target did not exist on the aggregate it - * will be added. - * - * @param {String} targetKey The unique key of the target target - * @param {Object} target The target target to update - */ + * Update the existing (if any) target in the aggregate with the given target. If the target did not exist on the aggregate it + * will be added. + * + * @param {String} targetKey The unique key of the target target + * @param {Object} target The target target to update + */ that.updateTarget = function(targetKey, target) { that.targets[targetKey] = target; }; /*! - * Add the given hash of actors to the given collection of actors. If any actors in the given set are already contained, they - * are not added/updated to the current set of actors. - * - * @param {Object} actors An object, keyed by the unique entity key, whose value is the actor to add to the current set of actors - */ + * Add the given hash of actors to the given collection of actors. If any actors in the given set are already contained, they + * are not added/updated to the current set of actors. + * + * @param {Object} actors An object, keyed by the unique entity key, whose value is the actor to add to the current set of actors + */ that.addActors = function(actors) { if (actors) { that.actors = _.extend(actors, that.actors); @@ -935,11 +921,11 @@ const ActivityAggregate = function(activityType, route, verb, published) { }; /*! - * Add the given hash of objects to the given collection of objects. If any objects in the given set are already contained, they - * are not added/updated to the current set of objects. - * - * @param {Object} objects An object, keyed by the unique entity key, whose value is the object to add to the current set of objects - */ + * Add the given hash of objects to the given collection of objects. If any objects in the given set are already contained, they + * are not added/updated to the current set of objects. + * + * @param {Object} objects An object, keyed by the unique entity key, whose value is the object to add to the current set of objects + */ that.addObjects = function(objects) { if (objects) { that.objects = _.extend(objects, that.objects); @@ -947,11 +933,11 @@ const ActivityAggregate = function(activityType, route, verb, published) { }; /*! - * Add the given hash of targets to the given collection of targets. If any targets in the given set are already contained, they - * are not added/updated to the current set of targets. - * - * @param {Object} targets An object, keyed by the unique entity key, whose value is the target to add to the current set of targets - */ + * Add the given hash of targets to the given collection of targets. If any targets in the given set are already contained, they + * are not added/updated to the current set of targets. + * + * @param {Object} targets An object, keyed by the unique entity key, whose value is the target to add to the current set of targets + */ that.addTargets = function(targets) { if (targets) { that.targets = _.extend(targets, that.targets); @@ -961,9 +947,4 @@ const ActivityAggregate = function(activityType, route, verb, published) { return that; }; -module.exports = { - resetAggregationForActivityStreams, - collectAllBuckets, - createAggregates, - createActivityEntity -}; +export { resetAggregationForActivityStreams, collectAllBuckets, createAggregates, createActivityEntity }; diff --git a/packages/oae-activity/lib/internal/buckets.js b/packages/oae-activity/lib/internal/buckets.js index 6dba2880c5..fe90063ad0 100644 --- a/packages/oae-activity/lib/internal/buckets.js +++ b/packages/oae-activity/lib/internal/buckets.js @@ -13,14 +13,16 @@ * permissions and limitations under the License. */ -const crypto = require('crypto'); -const util = require('util'); -const _ = require('underscore'); +import crypto from 'crypto'; +import util from 'util'; +import _ from 'underscore'; -const Locking = require('oae-util/lib/locking'); -const log = require('oae-logger').logger('oae-activity-buckets'); -const OAE = require('oae-util/lib/oae'); -const TelemetryAPI = require('oae-telemetry'); +import * as Locking from 'oae-util/lib/locking'; +import { logger } from 'oae-logger'; +import OAE from 'oae-util/lib/oae'; +import * as TelemetryAPI from 'oae-telemetry'; + +const log = logger('oae-activity-buckets'); // Holds the current amount of buckets that are being collected and telemtry object per type of bucket const bucketsInfo = {}; @@ -34,9 +36,7 @@ let shuttingDown = false; * we can ensure that is to force it to stop after the current batch. */ OAE.registerPreShutdownHandler('oae-activity-buckets', null, callback => { - log().info( - 'Enabling shutdown status to abort any current bucket collections as soon as possible' - ); + log().info('Enabling shutdown status to abort any current bucket collections as soon as possible'); shuttingDown = true; return callback(); }); @@ -105,12 +105,11 @@ const collectAllBuckets = function( }; // Ensure we don't surpass the maximum number of concurrent collections - if ( - bucketsInfo[type].currentConcurrentCollectionCount >= bucketsInfo[type].maxConcurrentCollections - ) { + if (bucketsInfo[type].currentConcurrentCollectionCount >= bucketsInfo[type].maxConcurrentCollections) { log().trace({ type }, 'Aborting collection due to max concurrent collections count reached'); return callback(); } + bucketsInfo[type].currentConcurrentCollectionCount++; // Fill all the possible bucket numbers to collect @@ -173,11 +172,7 @@ const _collectBuckets = function(type, bucketNumbers, callback, _errs) { */ const _collectBucket = function(type, bucketNumber, callback) { if (shuttingDown) { - log().info( - { type }, - 'Aborting bucket collection of bucket %s as shutdown is in progress', - bucketNumber - ); + log().info({ type }, 'Aborting bucket collection of bucket %s as shutdown is in progress', bucketNumber); return callback(); } @@ -190,6 +185,7 @@ const _collectBucket = function(type, bucketNumber, callback) { if (err) { return callback(err); } + if (!lockId) { // We could not acquire a lock, someone else came around and managed to snag the bucket return callback(); @@ -204,6 +200,7 @@ const _collectBucket = function(type, bucketNumber, callback) { if (collectionErr) { return callback(collectionErr); } + if (releaseErr) { log().warn( { err: releaseErr, type }, @@ -216,11 +213,7 @@ const _collectBucket = function(type, bucketNumber, callback) { return callback(releaseErr); } - log().trace( - { lockId, type }, - 'Successfully released lock for bucket number %s', - bucketNumber - ); + log().trace({ lockId, type }, 'Successfully released lock for bucket number %s', bucketNumber); if (!hadLock) { // This means that the lock expired before we finished collecting, which likely means the lock expiry @@ -261,7 +254,4 @@ const _getLockKey = function(type, bucketNumber) { return util.format('oae-activity:%s:lock-%s', type, bucketNumber); }; -module.exports = { - getBucketNumber, - collectAllBuckets -}; +export { getBucketNumber, collectAllBuckets }; diff --git a/packages/oae-activity/lib/internal/config.js b/packages/oae-activity/lib/internal/config.js index ec6811db45..437bc0cd24 100644 --- a/packages/oae-activity/lib/internal/config.js +++ b/packages/oae-activity/lib/internal/config.js @@ -13,11 +13,13 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const log = require('oae-logger').logger('oae-activity-config'); -const OaeUtil = require('oae-util/lib/util'); +import { logger } from 'oae-logger'; +import * as OaeUtil from 'oae-util/lib/util'; + +const log = logger('oae-activity-config'); const DEFAULT_ACTIVITY_TTL = 2 * 7 * 24 * 60 * 60; // 2 weeks (in seconds) const DEFAULT_AGGREGATE_IDLE_EXPIRY = 3 * 60 * 60; // 3 hours (in seconds) @@ -56,31 +58,19 @@ const refreshConfiguration = function(_config) { _config.numberOfProcessingBuckets, DEFAULT_NUMBER_OF_PROCESSING_BUCKETS ), - aggregateIdleExpiry: OaeUtil.getNumberParam( - _config.aggregateIdleExpiry, - DEFAULT_AGGREGATE_IDLE_EXPIRY - ), - aggregateMaxExpiry: OaeUtil.getNumberParam( - _config.aggregateMaxExpiry, - DEFAULT_AGGREGATE_MAX_EXPIRY - ), + aggregateIdleExpiry: OaeUtil.getNumberParam(_config.aggregateIdleExpiry, DEFAULT_AGGREGATE_IDLE_EXPIRY), + aggregateMaxExpiry: OaeUtil.getNumberParam(_config.aggregateMaxExpiry, DEFAULT_AGGREGATE_MAX_EXPIRY), collectionExpiry: OaeUtil.getNumberParam(_config.collectionExpiry, DEFAULT_COLLECTION_EXPIRY), maxConcurrentCollections: OaeUtil.getNumberParam( _config.maxConcurrentCollections, DEFAULT_MAX_CONCURRENT_COLLECTIONS ), - maxConcurrentRouters: OaeUtil.getNumberParam( - _config.maxConcurrentRouters, - DEFAULT_MAX_CONCURRENT_ROUTERS - ), + maxConcurrentRouters: OaeUtil.getNumberParam(_config.maxConcurrentRouters, DEFAULT_MAX_CONCURRENT_ROUTERS), collectionPollingFrequency: OaeUtil.getNumberParam( _config.collectionPollingFrequency, DEFAULT_COLLECTION_POLLING_FREQUENCY ), - collectionBatchSize: OaeUtil.getNumberParam( - _config.collectionBatchSize, - DEFAULT_COLLECTION_BATCH_SIZE - ), + collectionBatchSize: OaeUtil.getNumberParam(_config.collectionBatchSize, DEFAULT_COLLECTION_BATCH_SIZE), mail: { pollingFrequency: OaeUtil.getNumberParam( _config.mail.pollingFrequency, @@ -122,7 +112,4 @@ const getConfig = function() { return config; }; -module.exports = { - refreshConfiguration, - getConfig -}; +export { refreshConfiguration, getConfig }; diff --git a/packages/oae-activity/lib/internal/dao.js b/packages/oae-activity/lib/internal/dao.js index f9091a0ab9..b24bc4c305 100644 --- a/packages/oae-activity/lib/internal/dao.js +++ b/packages/oae-activity/lib/internal/dao.js @@ -13,17 +13,20 @@ * permissions and limitations under the License. */ -const crypto = require('crypto'); -const util = require('util'); -const _ = require('underscore'); -const ShortId = require('shortid'); +import crypto from 'crypto'; +import util from 'util'; +import _ from 'underscore'; +import ShortId from 'shortid'; -const Cassandra = require('oae-util/lib/cassandra'); -const log = require('oae-logger').logger('oae-activity'); -const OaeUtil = require('oae-util/lib/util'); +import * as Cassandra from 'oae-util/lib/cassandra'; -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivitySystemConfig = require('./config'); +import { logger } from 'oae-logger'; +import * as OaeUtil from 'oae-util/lib/util'; + +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as ActivitySystemConfig from './config'; + +const log = logger('oae-activity'); // The redis client that will be used for storing / fetch aggregate entities let redisClient = null; @@ -106,10 +109,7 @@ const getActivitiesFromStreams = function(activityStreamIds, start, callback) { activitiesPerStream[activityStreamId].push(activity); } catch (error) { activityStr = activityStr.slice(0, 300); - log().warn( - { err: error, activityId, value: activityStr }, - 'Error parsing activity from Cassandra' - ); + log().warn({ err: error, activityId, value: activityStr }, 'Error parsing activity from Cassandra'); } }); @@ -200,11 +200,7 @@ const deleteActivities = function(routeActivityIds, callback) { * @param {Object} callback.err An error that occurred, if any */ const clearActivityStream = function(activityStreamId, callback) { - Cassandra.runQuery( - 'DELETE FROM "ActivityStreams" WHERE "activityStreamId" = ?', - [activityStreamId], - callback - ); + Cassandra.runQuery('DELETE FROM "ActivityStreams" WHERE "activityStreamId" = ?', [activityStreamId], callback); }; /** @@ -413,17 +409,12 @@ const indexAggregateData = function(statusUpdatesByActivityStreamId, callback) { keysToExpire.push(statusKey); // Keep track of which aggregates are active for an activity stream, so we can reset aggregation per stream - const activeAggregatesForActivityStreamKey = _createActiveAggregatesForActivityStreamKey( - activityStreamId - ); + const activeAggregatesForActivityStreamKey = _createActiveAggregatesForActivityStreamKey(activityStreamId); multi.zadd(activeAggregatesForActivityStreamKey, Date.now(), aggregateKey); // An aggregate will expire after `aggregateMaxExpiry` seconds // There is no point in keeping this set around for any longer than that - multi.expire( - activeAggregatesForActivityStreamKey, - ActivitySystemConfig.getConfig().aggregateMaxExpiry - ); + multi.expire(activeAggregatesForActivityStreamKey, ActivitySystemConfig.getConfig().aggregateMaxExpiry); // Although we expire the set, it might contain expired items // Every time we add something in the set, we remove anything that has expired @@ -474,60 +465,47 @@ const deleteAggregateData = function(aggregateKeys, callback) { */ const resetAggregationForActivityStreams = function(activityStreamIds, callback) { // 1. Get all the active aggregates for these activity streams - getActiveAggregateKeysForActivityStreams( - activityStreamIds, - (err, activeAggregateKeysForActivityStreams) => { - if (err) { - log().error( - { err, activityStreamIds }, - 'Failed to get the active aggregate keys for a set of activity streams' - ); - return callback(err); + getActiveAggregateKeysForActivityStreams(activityStreamIds, (err, activeAggregateKeysForActivityStreams) => { + if (err) { + log().error({ err, activityStreamIds }, 'Failed to get the active aggregate keys for a set of activity streams'); + return callback(err); + } + + // 2. Remove the active aggregates + const multi = redisClient.multi(); + let allActiveAggregateKeys = []; + _.each(activeAggregateKeysForActivityStreams, (activeAggregateKeys, index) => { + const activityStream = activityStreamIds[index]; + if (!_.isEmpty(activeAggregateKeys)) { + // As we will be removing these aggregate keys, they will no longer be active for this stream so we can remove them from the "current active aggregate keys" set + const activeAggregatesForActivityStreamKey = _createActiveAggregatesForActivityStreamKey(activityStream); + multi.zrem(activeAggregatesForActivityStreamKey, activeAggregateKeys); + + // Keep track of all the active aggregate keys across activitystreams so we can generate the status and entity cache keys + // This allows us to delete them in one big `del` command + allActiveAggregateKeys = allActiveAggregateKeys.concat(activeAggregateKeys); } + }); - // 2. Remove the active aggregates - const multi = redisClient.multi(); - let allActiveAggregateKeys = []; - _.each(activeAggregateKeysForActivityStreams, (activeAggregateKeys, index) => { - const activityStream = activityStreamIds[index]; - if (!_.isEmpty(activeAggregateKeys)) { - // As we will be removing these aggregate keys, they will no longer be active for this stream so we can remove them from the "current active aggregate keys" set - const activeAggregatesForActivityStreamKey = _createActiveAggregatesForActivityStreamKey( - activityStream - ); - multi.zrem(activeAggregatesForActivityStreamKey, activeAggregateKeys); - - // Keep track of all the active aggregate keys across activitystreams so we can generate the status and entity cache keys - // This allows us to delete them in one big `del` command - allActiveAggregateKeys = allActiveAggregateKeys.concat(activeAggregateKeys); - } - }); + // Delete all the active aggregate keys if there are any + const activeAggregateCacheKeysToDelete = _getAggregateCacheKeysForAggregateKeys(allActiveAggregateKeys); + if (_.isEmpty(activeAggregateCacheKeysToDelete)) { + return callback(); + } - // Delete all the active aggregate keys if there are any - const activeAggregateCacheKeysToDelete = _getAggregateCacheKeysForAggregateKeys( - allActiveAggregateKeys - ); - if (_.isEmpty(activeAggregateCacheKeysToDelete)) { - return callback(); + multi.del(activeAggregateCacheKeysToDelete); + multi.exec(err => { + if (err) { + log().error({ err, activityStreamIds }, 'Failed to reset aggregation for a set of activity streams'); + return callback({ + code: 500, + msg: 'Failed to reset aggregation for a set of activity streams' + }); } - multi.del(activeAggregateCacheKeysToDelete); - multi.exec(err => { - if (err) { - log().error( - { err, activityStreamIds }, - 'Failed to reset aggregation for a set of activity streams' - ); - return callback({ - code: 500, - msg: 'Failed to reset aggregation for a set of activity streams' - }); - } - - return callback(); - }); - } - ); + return callback(); + }); + }); }; /** @@ -547,10 +525,7 @@ const getActiveAggregateKeysForActivityStreams = function(activityStreams, callb }); multi.exec((err, activeAggregateKeysForActivityStreams) => { if (err) { - log().error( - { err, activityStreams }, - 'Failed to get the active aggregate keys for a set of activity streams' - ); + log().error({ err, activityStreams }, 'Failed to get the active aggregate keys for a set of activity streams'); return callback({ code: 500, msg: 'Failed to get the active aggregate keys for a set of activity streams' @@ -596,55 +571,55 @@ const getAggregatedEntities = function(aggregateKeys, callback) { } /*! - * Each aggregate key has actors, objects and targets associated to it, and each of those are stored as separate cache entries. For - * example: - * - * ``` - * "aggregateKey0:actors": { - * "": "", - * "": "" - * } - * - * "aggregateKey0:objects": { - * "": "" - * } - * - * "aggregateKey0:targets": { - * "": "" - * } - * ``` - * - * The "aggregateKey0:actors" value is a cache key and its value is a Redis "hash". The entity keys in the hashes represent all - * entities that have been aggregated for the associated aggregate key + entity type. The value of the hash is a cache key that - * can be used to fetch the entity that represents the entity keyed by the entity key. - * - * To get all the aggregated entities for an aggregate key, we have to use a redis multi command and parse the result. Here is - * an example: - * - * ``` - * multi - * hgetall "aggregateKey0:actors" - * hgetall "aggregateKey0:objects" - * hgetall "aggregateKey0:targets" - * exec - * ``` - * - * This fetches all the actors, objects and targets. The result of this will be an array of the results of each command, separated by a new line: - * - * ``` - * [ - * { - * "": "", - * "": "" - * }, - * { "": "" }, - * { "": "" } - * ] - * ``` - * - * To construct the result of the query, a second command is needed to fetch the entities that are represented by the entity identities. That is - * performed using a Redis multi-get ("mget"), by `_fetchEntitiesByIdentities`. - */ + * Each aggregate key has actors, objects and targets associated to it, and each of those are stored as separate cache entries. For + * example: + * + * ``` + * "aggregateKey0:actors": { + * "": "", + * "": "" + * } + * + * "aggregateKey0:objects": { + * "": "" + * } + * + * "aggregateKey0:targets": { + * "": "" + * } + * ``` + * + * The "aggregateKey0:actors" value is a cache key and its value is a Redis "hash". The entity keys in the hashes represent all + * entities that have been aggregated for the associated aggregate key + entity type. The value of the hash is a cache key that + * can be used to fetch the entity that represents the entity keyed by the entity key. + * + * To get all the aggregated entities for an aggregate key, we have to use a redis multi command and parse the result. Here is + * an example: + * + * ``` + * multi + * hgetall "aggregateKey0:actors" + * hgetall "aggregateKey0:objects" + * hgetall "aggregateKey0:targets" + * exec + * ``` + * + * This fetches all the actors, objects and targets. The result of this will be an array of the results of each command, separated by a new line: + * + * ``` + * [ + * { + * "": "", + * "": "" + * }, + * { "": "" }, + * { "": "" } + * ] + * ``` + * + * To construct the result of the query, a second command is needed to fetch the entities that are represented by the entity identities. That is + * performed using a Redis multi-get ("mget"), by `_fetchEntitiesByIdentities`. + */ // Collect all the hgetall commands for the actor, object and targets of each aggregate key and execute it const multiGetAggregateEntities = redisClient.multi(); @@ -707,10 +682,7 @@ const getAggregatedEntities = function(aggregateKeys, callback) { targets: {} }; - log().trace( - { aggregate: result }, - 'Iterating aggregated entity identities to map to full entities' - ); + log().trace({ aggregate: result }, 'Iterating aggregated entity identities to map to full entities'); _.each(result, (identity, entityKey) => { // Grab the entity from the identity map that was fetched @@ -757,10 +729,7 @@ const _fetchEntitiesByIdentities = function(entityIdentities, callback) { try { entitiesByIdentity[entityIdentities[i]] = JSON.parse(entityStr); } catch (error) { - log().warn( - { entityStr }, - 'Failed to parse aggregated activity entity from redis. Skipping.' - ); + log().warn({ entityStr }, 'Failed to parse aggregated activity entity from redis. Skipping.'); } } }); @@ -792,8 +761,8 @@ const saveAggregatedEntities = function(aggregates, callback) { log().trace({ aggregates }, 'Saving aggregate entities.'); /*! - * For details on how these are persisted in redis, see the large summary comment within #getAggregatedEntities - */ + * For details on how these are persisted in redis, see the large summary comment within #getAggregatedEntities + */ const multi = redisClient.multi(); _.each(aggregates, (aggregate, aggregateKey) => { @@ -995,6 +964,7 @@ const unqueueUsersForEmail = function(bucketId, userIds, callback) { if (_.isEmpty(userIds)) { return callback(); } + const queries = _.map(userIds, userId => { return { query: 'DELETE FROM "EmailBuckets" WHERE "bucketId" = ? AND "userId" = ?', @@ -1056,14 +1026,14 @@ const incrementNotificationsUnreadCounts = function(userIdsIncrBy, callback) { } /*! - * The result is the new counts for the cache keys, separated by new line: - * - * - * 7 - * - * 3 - * ... - */ + * The result is the new counts for the cache keys, separated by new line: + * + * + * 7 + * + * 3 + * ... + */ const newValues = {}; _.each(results, (newValue, i) => { newValues[userIds[i]] = newValue; @@ -1089,10 +1059,7 @@ const _rowsToActivities = function(rows) { activities.push(JSON.parse(activityStr)); } catch (error) { activityStr = activityStr.slice(0, 300); - log().warn( - { err: error, activityId, value: activityStr }, - 'Error parsing activity from Cassandra' - ); + log().warn({ err: error, activityId, value: activityStr }, 'Error parsing activity from Cassandra'); } }); return activities; @@ -1217,7 +1184,7 @@ const _createActiveAggregatesForActivityStreamKey = function(activityStream) { return util.format('oae-activity:active-aggregates:%s', activityStream); }; -module.exports = { +export { init, getActivities, getActivitiesFromStreams, diff --git a/packages/oae-activity/lib/internal/email.js b/packages/oae-activity/lib/internal/email.js index e664f4f148..75d461ade7 100644 --- a/packages/oae-activity/lib/internal/email.js +++ b/packages/oae-activity/lib/internal/email.js @@ -13,36 +13,40 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); - -const AuthzInvitationsDAO = require('oae-authz/lib/invitations/dao'); -const AuthzUtil = require('oae-authz/lib/util'); -const { Context } = require('oae-context'); -const Counter = require('oae-util/lib/counter'); -const EmailAPI = require('oae-email'); -const OaeUtil = require('oae-util/lib/util'); -const { PrincipalsConstants } = require('oae-principals/lib/constants'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const PrincipalsEmitter = require('oae-principals/lib/internal/emitter'); -const Sanitization = require('oae-util/lib/sanitization'); -const Telemetry = require('oae-telemetry').telemetry('activity-email'); -const TenantsAPI = require('oae-tenants'); -const TenantConfig = require('oae-config').config('oae-tenants'); -const TenantsUtil = require('oae-tenants/lib/util'); -const TZ = require('oae-util/lib/tz'); -const UIAPI = require('oae-ui'); - -const log = require('oae-logger').logger('oae-activity-email'); -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityModel = require('oae-activity/lib/model'); -const ActivityUtil = require('oae-activity/lib/util'); -const ActivitySystemConfig = require('./config'); -const ActivityTransformer = require('./transformer'); -const ActivityEmitter = require('./emitter'); -const ActivityDAO = require('./dao'); -const ActivityBuckets = require('./buckets'); -const ActivityAggregator = require('./aggregator'); +import util from 'util'; +import _ from 'underscore'; + +import * as AuthzInvitationsDAO from 'oae-authz/lib/invitations/dao'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import { Context } from 'oae-context'; +import Counter from 'oae-util/lib/counter'; +import * as EmailAPI from 'oae-email'; +import * as OaeUtil from 'oae-util/lib/util'; +import { PrincipalsConstants } from 'oae-principals/lib/constants'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import PrincipalsEmitter from 'oae-principals/lib/internal/emitter'; +import * as Sanitization from 'oae-util/lib/sanitization'; +import { telemetry } from 'oae-telemetry'; +import * as TenantsAPI from 'oae-tenants'; +import { setUpConfig } from 'oae-config'; +import * as TenantsUtil from 'oae-tenants/lib/util'; +import * as TZ from 'oae-util/lib/tz'; +import * as UIAPI from 'oae-ui'; + +import { logger } from 'oae-logger'; +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as ActivityModel from 'oae-activity/lib/model'; +import * as ActivityUtil from 'oae-activity/lib/util'; +import * as ActivitySystemConfig from './config'; +import * as ActivityTransformer from './transformer'; +import ActivityEmitter from './emitter'; +import * as ActivityDAO from './dao'; +import * as ActivityBuckets from './buckets'; +import * as ActivityAggregator from './aggregator'; + +const Telemetry = telemetry('activity-email'); +const TenantConfig = setUpConfig('oae-tenants'); +const log = logger('oae-activity-email'); // The maximum amount of users can be handled during a bucket collection const MAX_COLLECTION_BATCH_SIZE = 50; @@ -153,6 +157,7 @@ PrincipalsEmitter.on(PrincipalsConstants.events.UPDATED_USER, (ctx, newUser, old // Users who opt out of email delivery shouldn't be queued for email delivery as they simply should not get email } + if (newUser.emailPreference === PrincipalsConstants.emailPreferences.NEVER) { return ActivityEmitter.emit(ActivityConstants.events.UPDATED_USER, ctx, newUser, oldUser); } @@ -277,6 +282,7 @@ const _collectDailyBucket = function(bucketNumber, callback) { callback ); } + return callback(null, true); }; @@ -301,6 +307,7 @@ const _collectWeeklyBucket = function(bucketNumber, callback) { callback ); } + return callback(null, true); }; @@ -346,6 +353,7 @@ const _isWeeklyCycle = function() { // check if the hour rolls over in this collection cycle return now.getHours() !== end.getHours(); } + return false; }; @@ -408,164 +416,142 @@ const collectMails = function(bucketNumber, emailPreference, dayOfWeek, hour, ca * @param {Resource} callback.recipients The recipients that received an email * @api private */ -const _collectMails = function( - bucketId, - oldestActivityTimestamp, - start, - callback, - _collectedRecipients -) { +const _collectMails = function(bucketId, oldestActivityTimestamp, start, callback, _collectedRecipients) { // Keep track of whom we sent a mail to _collectedRecipients = _collectedRecipients || []; // 1. Get all the recipients who are queued for email delivery - ActivityDAO.getQueuedUserIdsForEmail( - bucketId, - start, - MAX_COLLECTION_BATCH_SIZE, - (err, recipientIds, nextToken) => { - if (err) { - return callback(err); - } - if (_.isEmpty(recipientIds)) { - return callback(null, []); - } + ActivityDAO.getQueuedUserIdsForEmail(bucketId, start, MAX_COLLECTION_BATCH_SIZE, (err, recipientIds, nextToken) => { + if (err) { + return callback(err); + } - const recipientIdsByActivityStreamIds = _.chain(recipientIds) - .map(recipientId => { - return [ - ActivityUtil.createActivityStreamId(recipientId, ActivityConstants.streams.EMAIL), - recipientId - ]; - }) - .object() - .value(); - - ActivityDAO.getActivitiesFromStreams( - _.keys(recipientIdsByActivityStreamIds), - oldestActivityTimestamp, - (err, activitiesPerStream) => { - if (err) { - return callback(err); - } + if (_.isEmpty(recipientIds)) { + return callback(null, []); + } - // Will hold the activities (keyed per stream) who have no activities within the grace period - const activitiesPerMailableStreams = {}; + const recipientIdsByActivityStreamIds = _.chain(recipientIds) + .map(recipientId => { + return [ActivityUtil.createActivityStreamId(recipientId, ActivityConstants.streams.EMAIL), recipientId]; + }) + .object() + .value(); + + ActivityDAO.getActivitiesFromStreams( + _.keys(recipientIdsByActivityStreamIds), + oldestActivityTimestamp, + (err, activitiesPerStream) => { + if (err) { + return callback(err); + } - // Will hold the user IDs that should receive an email during this collection phase - const recipientIdsToMail = []; + // Will hold the activities (keyed per stream) who have no activities within the grace period + const activitiesPerMailableStreams = {}; - // 3. Filter the activity streams to those who have no activities within the grace period - const threshold = Date.now() - ActivitySystemConfig.getConfig().mail.gracePeriod * 1000; - _.each(activitiesPerStream, (activities, activityStreamId) => { - const hasRecentActivity = _.find(activities, activity => { - return activity.published > threshold; - }); + // Will hold the user IDs that should receive an email during this collection phase + const recipientIdsToMail = []; + + // 3. Filter the activity streams to those who have no activities within the grace period + const threshold = Date.now() - ActivitySystemConfig.getConfig().mail.gracePeriod * 1000; + _.each(activitiesPerStream, (activities, activityStreamId) => { + const hasRecentActivity = _.find(activities, activity => { + return activity.published > threshold; + }); + + if (!hasRecentActivity) { + // Keep track of the activities for which we'll send out an e-mail + activitiesPerMailableStreams[activityStreamId] = activities; - if (!hasRecentActivity) { - // Keep track of the activities for which we'll send out an e-mail - activitiesPerMailableStreams[activityStreamId] = activities; + // Keep track of the id of the user for which we'll send out an e-mail + recipientIdsToMail.push(recipientIdsByActivityStreamIds[activityStreamId]); + } + }); + + // 4. Reset aggregation for the those streams we'll be sending out an email for + // so that the next e-mail doesn't contain the same activities + ActivityDAO.resetAggregationForActivityStreams(_.keys(activitiesPerMailableStreams), err => { + if (err) { + return callback(err); + } - // Keep track of the id of the user for which we'll send out an e-mail - recipientIdsToMail.push(recipientIdsByActivityStreamIds[activityStreamId]); + // 5. Remove the users we'll email from the buckets + ActivityDAO.unqueueUsersForEmail(bucketId, recipientIdsToMail, err => { + if (err) { + return callback(err); } - }); - // 4. Reset aggregation for the those streams we'll be sending out an email for - // so that the next e-mail doesn't contain the same activities - ActivityDAO.resetAggregationForActivityStreams( - _.keys(activitiesPerMailableStreams), - err => { + // 6. Delete these activities as we will be pushing them out + const activitiesPerStreamToDelete = {}; + _.each(activitiesPerMailableStreams, (activities, activityStreamId) => { + activitiesPerStreamToDelete[activityStreamId] = _.pluck( + activities, + ActivityConstants.properties.OAE_ACTIVITY_ID + ); + }); + ActivityDAO.deleteActivities(activitiesPerStreamToDelete, err => { if (err) { return callback(err); } - // 5. Remove the users we'll email from the buckets - ActivityDAO.unqueueUsersForEmail(bucketId, recipientIdsToMail, err => { + // Get all the recipient profiles + _getEmailRecipientResources(recipientIdsToMail, (err, recipients) => { if (err) { + log().error( + { err, recipientIds: recipientIdsToMail }, + 'Failed to get the recipients when sending email' + ); return callback(err); } - // 6. Delete these activities as we will be pushing them out - const activitiesPerStreamToDelete = {}; - _.each(activitiesPerMailableStreams, (activities, activityStreamId) => { - activitiesPerStreamToDelete[activityStreamId] = _.pluck( - activities, - ActivityConstants.properties.OAE_ACTIVITY_ID + // Keep track of whom we need to email + const recipientsToMail = _.filter(recipients, recipient => { + // Although it's very unlikely that a user who changed their email preferences would end up here, + // we take it into account as it would be really unfortunate to send them any further email. Additionally, + // if the email stream for the user was empty (because they marked their notifications as read), we can't + // send them any mail either + const emailActivityStreamId = ActivityUtil.createActivityStreamId( + recipient.id, + ActivityConstants.streams.EMAIL + ); + return ( + recipient.emailPreference !== PrincipalsConstants.emailPreferences.NEVER && + !_.isEmpty(activitiesPerMailableStreams[emailActivityStreamId]) ); }); - ActivityDAO.deleteActivities(activitiesPerStreamToDelete, err => { + + // Transform the recipients array so the activities are included + const toMail = _.map(recipientsToMail, recipient => { + const emailActivityStreamId = ActivityUtil.createActivityStreamId( + recipient.id, + ActivityConstants.streams.EMAIL + ); + return { + recipient, + activities: activitiesPerMailableStreams[emailActivityStreamId] + }; + }); + + // 7. Send out the emails + _mailAll(toMail, err => { if (err) { return callback(err); } - // Get all the recipient profiles - _getEmailRecipientResources(recipientIdsToMail, (err, recipients) => { - if (err) { - log().error( - { err, recipientIds: recipientIdsToMail }, - 'Failed to get the recipients when sending email' - ); - return callback(err); - } - - // Keep track of whom we need to email - const recipientsToMail = _.filter(recipients, recipient => { - // Although it's very unlikely that a user who changed their email preferences would end up here, - // we take it into account as it would be really unfortunate to send them any further email. Additionally, - // if the email stream for the user was empty (because they marked their notifications as read), we can't - // send them any mail either - const emailActivityStreamId = ActivityUtil.createActivityStreamId( - recipient.id, - ActivityConstants.streams.EMAIL - ); - return ( - recipient.emailPreference !== PrincipalsConstants.emailPreferences.NEVER && - !_.isEmpty(activitiesPerMailableStreams[emailActivityStreamId]) - ); - }); - - // Transform the recipients array so the activities are included - const toMail = _.map(recipientsToMail, recipient => { - const emailActivityStreamId = ActivityUtil.createActivityStreamId( - recipient.id, - ActivityConstants.streams.EMAIL - ); - return { - recipient, - activities: activitiesPerMailableStreams[emailActivityStreamId] - }; - }); - - // 7. Send out the emails - _mailAll(toMail, err => { - if (err) { - return callback(err); - } - - _collectedRecipients = _collectedRecipients.concat(recipientsToMail); - - if (nextToken) { - _collectMails( - bucketId, - oldestActivityTimestamp, - nextToken, - callback, - _collectedRecipients - ); - } else { - return callback(null, _collectedRecipients); - } - }); - }); + _collectedRecipients = _collectedRecipients.concat(recipientsToMail); + + if (nextToken) { + _collectMails(bucketId, oldestActivityTimestamp, nextToken, callback, _collectedRecipients); + } else { + return callback(null, _collectedRecipients); + } }); }); - } - ); - } - ); - } - ); + }); + }); + }); + } + ); + }); }; /** @@ -618,9 +604,7 @@ const _mail = function(recipient, activities, callback) { const timezone = TenantConfig.getValue(tenant.alias, 'timezone', 'timezone'); const ctx = new Context(tenant, recipient); if (!tenant.active) { - return callback( - new Error(util.format('Tried to email a user in a disabled tenancy %s', tenant.alias)) - ); + return callback(new Error(util.format('Tried to email a user in a disabled tenancy %s', tenant.alias))); } ActivityTransformer.transformActivities( @@ -643,24 +627,14 @@ const _mail = function(recipient, activities, callback) { invitationEmailParam ); - invitationUrl = util.format( - '%s/signup?url=%s', - baseUrl, - encodeURIComponent(signupRedirectUrl) - ); + invitationUrl = util.format('%s/signup?url=%s', baseUrl, encodeURIComponent(signupRedirectUrl)); } // Transform the activities in a simple model that the templates can use to generate the email const ActivityAdapter = UIAPI.getActivityAdapter(); - const adaptedActivities = ActivityAdapter.adapt( - recipient.id, - recipient, - aggregatedActivities, - Sanitization, - { - resourceHrefOverride: invitationUrl - } - ); + const adaptedActivities = ActivityAdapter.adapt(recipient.id, recipient, aggregatedActivities, Sanitization, { + resourceHrefOverride: invitationUrl + }); // Generate a unique fingerprint for this mail so we don't accidentally send it out multiple times // We cannot use the activityId as each activity gets a new ID when routed and/or aggregated @@ -684,14 +658,7 @@ const _mail = function(recipient, activities, callback) { timezone }; - return EmailAPI.sendEmail( - 'oae-activity', - 'mail', - recipient, - data, - { hash: emailHash }, - callback - ); + return EmailAPI.sendEmail('oae-activity', 'mail', recipient, data, { hash: emailHash }, callback); } ); }; @@ -737,10 +704,7 @@ const _aggregate = function(userId, activities) { // Pass 2: Select all the single-aggregates, filter out those that are consumed in a multi-aggregate and push the remainder in the aggregated activities set _.each(aggregates, aggregate => { - if ( - aggregate.activityIds.length === 1 && - !_.contains(aggregatedActivityIds, aggregate.activityIds[0]) - ) { + if (aggregate.activityIds.length === 1 && !_.contains(aggregatedActivityIds, aggregate.activityIds[0])) { aggregatedActivities.push(_createActivityFromAggregate(aggregate)); aggregatedActivityIds.push(aggregate.activityIds[0]); } @@ -846,9 +810,11 @@ const _getEntities = function(entity) { if (!entity) { return []; } + if (entity.objectType !== 'collection') { return [entity]; } + return entity[ActivityConstants.properties.OAE_COLLECTION]; }; @@ -870,49 +836,44 @@ const _getEmailRecipientResources = function(emailRecipientIds, callback) { const emails = _.last(recipientIdsPartitioned); // If there are any emails, get the invitation tokens that are associated to them - OaeUtil.invokeIfNecessary( - !_.isEmpty(emails), - AuthzInvitationsDAO.getTokensByEmails, - emails, - (err, tokensByEmail) => { - if (err) { - return callback(err); - } - - // Derive a resource profile for the email, where the tenant is the tenant whose configured - // email domain matches the email address - const emailResources = _.map(emails, email => { - return { - id: email, - tenant: TenantsAPI.getTenantByEmail(email), - email, - emailPreference: PrincipalsConstants.emailPreferences.IMMEDIATE, - token: tokensByEmail[email] - }; - }); + OaeUtil.invokeIfNecessary(!_.isEmpty(emails), AuthzInvitationsDAO.getTokensByEmails, emails, (err, tokensByEmail) => { + if (err) { + return callback(err); + } - // If there were user recipients, get the user profiles - OaeUtil.invokeIfNecessary( - !_.isEmpty(userIds), - PrincipalsDAO.getPrincipals, - userIds, - ['principalId', 'tenantAlias', 'deleted', 'email', 'emailPreference'], - (err, usersById) => { - if (err) { - return callback(err); - } + // Derive a resource profile for the email, where the tenant is the tenant whose configured + // email domain matches the email address + const emailResources = _.map(emails, email => { + return { + id: email, + tenant: TenantsAPI.getTenantByEmail(email), + email, + emailPreference: PrincipalsConstants.emailPreferences.IMMEDIATE, + token: tokensByEmail[email] + }; + }); - return callback( - null, - _.chain(usersById) - .values() - .union(emailResources) - .value() - ); + // If there were user recipients, get the user profiles + OaeUtil.invokeIfNecessary( + !_.isEmpty(userIds), + PrincipalsDAO.getPrincipals, + userIds, + ['principalId', 'tenantAlias', 'deleted', 'email', 'emailPreference'], + (err, usersById) => { + if (err) { + return callback(err); } - ); - } - ); + + return callback( + null, + _.chain(usersById) + .values() + .union(emailResources) + .value() + ); + } + ); + }); }; /** @@ -944,17 +905,17 @@ const _createEmailBucketIdForRecipient = function(recipient) { // Anything else than immediate delivery needs to be scheduled appropriately if (recipient.emailPreference !== PrincipalsConstants.emailPreferences.IMMEDIATE) { /*! - * Take the given timezone into account for daily and/or weekly mails. We try to deliver an e-mail - * at the configured hour in the given timezone. In order to do this we need the timezone offset - * between the given timezone and UTC as buckets are always handled in UTC. - * - * For example, the given timezone is Miami (UTC-5) and mail needs to be delivered at 13h. The mail - * needs to leave the server at 18h (UTC time) so it arrives when it's 13h in Miami. - * - * For weekly emails, it's entirely possible we need to schedule email delivery on the previous/next day. - * For example, the given timezone is Islamabad (UTC+5) and mails need to be delivered at 2am. The mail - * needs to leave the server at 21h (UTC time) so it arrives when it's 2am in Islamabad - */ + * Take the given timezone into account for daily and/or weekly mails. We try to deliver an e-mail + * at the configured hour in the given timezone. In order to do this we need the timezone offset + * between the given timezone and UTC as buckets are always handled in UTC. + * + * For example, the given timezone is Miami (UTC-5) and mail needs to be delivered at 13h. The mail + * needs to leave the server at 18h (UTC time) so it arrives when it's 13h in Miami. + * + * For weekly emails, it's entirely possible we need to schedule email delivery on the previous/next day. + * For example, the given timezone is Islamabad (UTC+5) and mails need to be delivered at 2am. The mail + * needs to leave the server at 21h (UTC time) so it arrives when it's 2am in Islamabad + */ const mailConfig = ActivitySystemConfig.getConfig().mail; // Get the offset between the timezone and UTC in hours. `getTimezoneOffset` returns the offset in minutes, @@ -994,9 +955,11 @@ const _getCollectionCycleStart = function(emailPreference) { if (emailPreference === PrincipalsConstants.emailPreferences.IMMEDIATE) { return Date.now() - ONE_HOUR_IN_MS; } + if (emailPreference === PrincipalsConstants.emailPreferences.DAILY) { return Date.now() - TWO_DAYS_IN_MS; } + if (emailPreference === PrincipalsConstants.emailPreferences.WEEKLY) { return Date.now() - TWO_WEEKS_IN_MS; } @@ -1016,17 +979,13 @@ const _createEmailBucketId = function(bucketNumber, emailPreference, dayOfWeek, if (emailPreference === PrincipalsConstants.emailPreferences.IMMEDIATE) { return util.format('oae-activity-email:%s:%s', bucketNumber, emailPreference); } + if (emailPreference === PrincipalsConstants.emailPreferences.DAILY) { return util.format('oae-activity-email:%s:%s:%d', bucketNumber, emailPreference, hour); } + if (emailPreference === PrincipalsConstants.emailPreferences.WEEKLY) { - return util.format( - 'oae-activity-email:%s:%s:%d:%d', - bucketNumber, - emailPreference, - dayOfWeek, - hour - ); + return util.format('oae-activity-email:%s:%s:%d:%d', bucketNumber, emailPreference, dayOfWeek, hour); } }; @@ -1042,8 +1001,4 @@ const _getBucketNumber = function(userId) { return ActivityBuckets.getBucketNumber(userId, numberOfBuckets); }; -module.exports = { - collectAllBuckets, - collectMails, - whenEmailsScheduled -}; +export { collectAllBuckets, collectMails, whenEmailsScheduled }; diff --git a/packages/oae-activity/lib/internal/emitter.js b/packages/oae-activity/lib/internal/emitter.js index 9441848683..237df621ea 100644 --- a/packages/oae-activity/lib/internal/emitter.js +++ b/packages/oae-activity/lib/internal/emitter.js @@ -13,7 +13,9 @@ * permissions and limitations under the License. */ -const EmitterAPI = require('oae-emitter'); +import * as EmitterAPI from 'oae-emitter'; // A singleton emitter for activities, so all internal APIs may produce and consume events -module.exports = new EmitterAPI.EventEmitter(); +const emitter = new EmitterAPI.EventEmitter(); + +export default emitter; diff --git a/packages/oae-activity/lib/internal/notifications.js b/packages/oae-activity/lib/internal/notifications.js index 8473fa743a..ce9a418182 100644 --- a/packages/oae-activity/lib/internal/notifications.js +++ b/packages/oae-activity/lib/internal/notifications.js @@ -13,18 +13,21 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const Counter = require('oae-util/lib/counter'); -const log = require('oae-logger').logger('oae-activity-notifications'); -const { PrincipalsConstants } = require('oae-principals/lib/constants'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); +import Counter from 'oae-util/lib/counter'; +import { logger } from 'oae-logger'; +import { PrincipalsConstants } from 'oae-principals/lib/constants'; +import PrincipalsDAO from 'oae-principals/lib/internal/dao'; -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityUtil = require('oae-activity/lib/util'); -const ActivityEmitter = require('./emitter'); -const ActivityDAO = require('./dao'); -const ActivityAggregator = require('./aggregator'); +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as ActivityUtil from 'oae-activity/lib/util'; +import ActivityEmitter from './emitter'; + +import * as ActivityDAO from './dao' +import * as ActivityAggregator from './aggregator' + +const log = logger('oae-activity-notifications');; // Tracks the handling of notifications for synchronization to determine when there are no // notifications being processed @@ -52,10 +55,7 @@ ActivityEmitter.on(ActivityConstants.events.DELIVERED_ACTIVITIES, deliveredActiv // All users receiving notifications will have their "notifications unread" counter incremented incrementNotificationsUnread(userIdsIncrBy, err => { if (err) { - log().error( - { err: new Error(err.msg), userIdsIncrBy }, - 'Could not mark notifications as unread' - ); + log().error({ err: new Error(err.msg), userIdsIncrBy }, 'Could not mark notifications as unread'); } // Our async operation is over, decrement the counter @@ -98,10 +98,7 @@ const markNotificationsRead = function(user, callback) { // Reset the aggregator for this user his notification stream. New notifications will not aggregate // with older notifications which will make it clearer to the user which activity is the new one - const notificationActivityStreamId = ActivityUtil.createActivityStreamId( - user.id, - 'notification' - ); + const notificationActivityStreamId = ActivityUtil.createActivityStreamId(user.id, 'notification'); ActivityAggregator.resetAggregationForActivityStreams([notificationActivityStreamId]); // By clearing a user's email activity stream when he marks his notifications as read, @@ -128,14 +125,14 @@ const markNotificationsRead = function(user, callback) { */ const incrementNotificationsUnread = function(userIdIncrs, callback) { /*! - * First update the cached new notification counts, then update Cassandra. Some very clear drawbacks here but - * are considered acceptable: - * - * 1. If 2 nodes increment and then persist to cassandra, and the first incr wins into cassandra, counts are - * off by 1. The next time a notification comes around it will be fixed. - * 2. If Redis is completely flushed or crashes with no disk storage, kiss all your counts good-bye. Will not - * become accurate again for a user until they "mark as read". - */ + * First update the cached new notification counts, then update Cassandra. Some very clear drawbacks here but + * are considered acceptable: + * + * 1. If 2 nodes increment and then persist to cassandra, and the first incr wins into cassandra, counts are + * off by 1. The next time a notification comes around it will be fixed. + * 2. If Redis is completely flushed or crashes with no disk storage, kiss all your counts good-bye. Will not + * become accurate again for a user until they "mark as read". + */ ActivityDAO.incrementNotificationsUnreadCounts(userIdIncrs, (err, newValues) => { if (err) { return callback(err); @@ -149,10 +146,10 @@ const incrementNotificationsUnread = function(userIdIncrs, callback) { } /*! - * Determines when the process of updating all principal counts in cassandra is complete. - * - * @param {Object} err An error that occurred, if any. - */ + * Determines when the process of updating all principal counts in cassandra is complete. + * + * @param {Object} err An error that occurred, if any. + */ const _monitorUpdatePrincipal = function(err) { if (complete) { // Nothing to do. @@ -170,11 +167,7 @@ const incrementNotificationsUnread = function(userIdIncrs, callback) { // Update all principal profiles with the new count _.each(newValues, (newValue, userId) => { - PrincipalsDAO.updatePrincipal( - userId, - { notificationsUnread: newValue.toString() }, - _monitorUpdatePrincipal - ); + PrincipalsDAO.updatePrincipal(userId, { notificationsUnread: newValue.toString() }, _monitorUpdatePrincipal); }); }); }; @@ -190,8 +183,4 @@ const whenNotificationsEmpty = function(handler) { notificationsCounter.whenZero(handler); }; -module.exports = { - markNotificationsRead, - incrementNotificationsUnread, - whenNotificationsEmpty -}; +export { markNotificationsRead, incrementNotificationsUnread, whenNotificationsEmpty }; diff --git a/packages/oae-activity/lib/internal/push.js b/packages/oae-activity/lib/internal/push.js index 2c7e212d0a..66c92fc9a5 100644 --- a/packages/oae-activity/lib/internal/push.js +++ b/packages/oae-activity/lib/internal/push.js @@ -13,25 +13,30 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const clone = require('clone'); -const selectn = require('selectn'); -const ShortId = require('shortid'); - -const { Context } = require('oae-context'); -const log = require('oae-logger').logger('oae-activity-push'); -const MQ = require('oae-util/lib/mq'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const Signature = require('oae-util/lib/signature'); -const Telemetry = require('oae-telemetry').telemetry('push'); -const TenantsAPI = require('oae-tenants'); -const { Validator } = require('oae-authz/lib/validator'); - -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityEmitter = require('oae-activity/lib/internal/emitter'); -const ActivityRegistry = require('oae-activity/lib/internal/registry'); -const ActivityTransformer = require('oae-activity/lib/internal/transformer'); -const ActivityUtil = require('oae-activity/lib/util'); +import ActivityEmitter from 'oae-activity/lib/internal/emitter'; + +import _ from 'underscore'; +import clone from 'clone'; +import selectn from 'selectn'; +import ShortId from 'shortid'; + +import { Context } from 'oae-context'; +import { logger } from 'oae-logger'; +import * as MQ from 'oae-util/lib/mq'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as Signature from 'oae-util/lib/signature'; +import { telemetry } from 'oae-telemetry'; +import * as TenantsAPI from 'oae-tenants'; +import { Validator } from 'oae-authz/lib/validator'; + +import { ActivityConstants } from 'oae-activity/lib/constants'; + +import * as ActivityRegistry from 'oae-activity/lib/internal/registry'; +import * as ActivityTransformer from 'oae-activity/lib/internal/transformer'; +import * as ActivityUtil from 'oae-activity/lib/util'; + +const log = logger('oae-activity-push'); +const Telemetry = telemetry('push'); /// /////////////////// // MODULE VARIABLES // @@ -184,12 +189,7 @@ const init = function(callback) { } // Subscribe to our queue for new events - return MQ.subscribeQueue( - queueName, - QueueConstants.subscribe.OPTIONS, - _handlePushActivity, - callback - ); + return MQ.subscribeQueue(queueName, QueueConstants.subscribe.OPTIONS, _handlePushActivity, callback); }); }); }; @@ -214,10 +214,10 @@ const registerConnection = function(socket) { Telemetry.incr('connection.count'); /*! - * The client has a 5 second window to authenticate itself, at which time this timeout will be - * cleared and the close function will not get executed. If the client does not authenticate, - * the connection is closed - */ + * The client has a 5 second window to authenticate itself, at which time this timeout will be + * cleared and the close function will not get executed. If the client does not authenticate, + * the connection is closed + */ connectionInfo.authenticationTimeout = setTimeout(() => { if (connectionInfo.ctx) { log().trace( @@ -238,10 +238,10 @@ const registerConnection = function(socket) { }, AUTHENTICATION_TIMEOUT); /*! - * A new message is pushed to us by the client - * - * @param {Object} msg The message the client sent us - */ + * A new message is pushed to us by the client + * + * @param {Object} msg The message the client sent us + */ socket.on('data', msg => { let message = null; try { @@ -284,12 +284,12 @@ const registerConnection = function(socket) { }); /*! - * A client disconnected. We need to do some clean-up. - * - * 1/ Unbind our RabbitMQ queue from the exchange for all the streams that user was interested in (but nobody else). - * - * 2/ Clear all local references to this socket, so we're not leaking memory. - */ + * A client disconnected. We need to do some clean-up. + * + * 1/ Unbind our RabbitMQ queue from the exchange for all the streams that user was interested in (but nobody else). + * + * 2/ Clear all local references to this socket, so we're not leaking memory. + */ socket.on('close', () => { log().trace({ sid: socket.id, streams: connectionInfo.streams }, 'Closing socket'); // Measure how long the clients stay connected @@ -380,9 +380,7 @@ const _authenticate = function(connectionInfo, message) { const validator = new Validator(); validator.check(data.tenantAlias, { code: 400, msg: 'A tenant needs to be provided' }).notEmpty(); validator.check(data.userId, { code: 400, msg: 'A userId needs to be provided' }).isUserId(); - validator - .check(null, { code: 400, msg: 'A signature object needs to be provided' }) - .isObject(data.signature); + validator.check(null, { code: 400, msg: 'A signature object needs to be provided' }).isObject(data.signature); if (validator.hasErrors()) { _writeResponse(connectionInfo, message.id, validator.getFirstError()); log().error({ err: validator.getFirstError() }, 'Invalid auth frame'); @@ -412,10 +410,7 @@ const _authenticate = function(connectionInfo, message) { PrincipalsDAO.getPrincipal(data.userId, (err, user) => { if (err) { _writeResponse(connectionInfo, message.id, err); - log().error( - { err, userId: data.userId, sid: socket.id }, - 'Error trying to get the principal object' - ); + log().error({ err, userId: data.userId, sid: socket.id }, 'Error trying to get the principal object'); return socket.close(); } @@ -432,10 +427,7 @@ const _authenticate = function(connectionInfo, message) { ) ) { _writeResponse(connectionInfo, message.id, { code: 401, msg: 'Invalid signature' }); - log().error( - { userId: data.userId, sid: socket.id }, - 'Incoming authentication signature was invalid' - ); + log().error({ userId: data.userId, sid: socket.id }, 'Incoming authentication signature was invalid'); return socket.close(); } @@ -496,28 +488,21 @@ const _subscribe = function(connectionInfo, message) { return _writeResponse(connectionInfo, message.id, err); } - const activityStreamId = ActivityUtil.createActivityStreamId( - data.stream.resourceId, - data.stream.streamType - ); + const activityStreamId = ActivityUtil.createActivityStreamId(data.stream.resourceId, data.stream.streamType); log().trace({ sid: socket.id, activityStreamId }, 'Registering socket for stream'); /*! - * Finishes up the subscription process and writes a response to the client - */ + * Finishes up the subscription process and writes a response to the client + */ const finish = function() { // Remember the desired transformer for this stream on this socket const transformerType = data.format || ActivityConstants.transformerTypes.INTERNAL; connectionInfo.transformerTypes = connectionInfo.transformerTypes || {}; - connectionInfo.transformerTypes[activityStreamId] = - connectionInfo.transformerTypes[activityStreamId] || []; + connectionInfo.transformerTypes[activityStreamId] = connectionInfo.transformerTypes[activityStreamId] || []; connectionInfo.transformerTypes[activityStreamId].push(transformerType); // Acknowledge a succesful subscription - log().trace( - { sid: socket.id, activityStreamId, format: transformerType }, - 'Registered a client for a stream' - ); + log().trace({ sid: socket.id, activityStreamId, format: transformerType }, 'Registered a client for a stream'); return _writeResponse(connectionInfo, message.id); }; @@ -526,16 +511,14 @@ const _subscribe = function(connectionInfo, message) { // No need to bind, we're already bound return finish(); } + // Remember this stream on the socket connectionInfo.streams.push(activityStreamId); // Bind our app queue to the exchange _bindQueue(activityStreamId, err => { if (err) { - log().error( - { sid: socket.id, err, activityStreamId }, - 'Could not bind our queue to the exchange' - ); + log().error({ sid: socket.id, err, activityStreamId }, 'Could not bind our queue to the exchange'); return _writeResponse(connectionInfo, message.id, err); } @@ -563,6 +546,7 @@ const _bindQueue = function(activityStreamId, callback) { if (connectionInfosPerStream[activityStreamId]) { return callback(); } + MQ.bindQueueToExchange(queueName, QueueConstants.exchange.NAME, activityStreamId, callback); // If we've seen this stream before, we're already listening for events for that stream @@ -581,6 +565,7 @@ const _getAuthorizationHandler = function(activityStreamId) { if (!streamOptions || !streamOptions.authorizationHandler) { return null; } + return streamOptions.authorizationHandler; }; @@ -630,12 +615,7 @@ const _writeResponse = function(connectionInfo, id, error) { * @api private */ const _push = function(activityStreamId, routedActivity) { - MQ.submit( - QueueConstants.exchange.NAME, - activityStreamId, - routedActivity, - QueueConstants.publish.OPTIONS - ); + MQ.submit(QueueConstants.exchange.NAME, activityStreamId, routedActivity, QueueConstants.publish.OPTIONS); }; /** @@ -658,32 +638,27 @@ const _handlePushActivity = function(data, callback) { todo++; // Because we're sending these activities to possible multiple sockets/users we'll need to clone and transform it for each socket const activities = clone(data.activities); - ActivityTransformer.transformActivities( - connectionInfo.ctx, - activities, - transformerType, - err => { - if (err) { - return log().error({ err }, 'Could not transform event'); - } + ActivityTransformer.transformActivities(connectionInfo.ctx, activities, transformerType, err => { + if (err) { + return log().error({ err }, 'Could not transform event'); + } - const msgData = { - resourceId: data.resourceId, - streamType: data.streamType, - activities, - format: transformerType, - numNewActivities: data.numNewActivities - }; - log().trace({ data: msgData, sid: socket.id }, 'Pushing message to socket'); - const msg = JSON.stringify(msgData); - socket.write(msg); + const msgData = { + resourceId: data.resourceId, + streamType: data.streamType, + activities, + format: transformerType, + numNewActivities: data.numNewActivities + }; + log().trace({ data: msgData, sid: socket.id }, 'Pushing message to socket'); + const msg = JSON.stringify(msgData); + socket.write(msg); - todo--; - if (todo === 0) { - callback(); - } + todo--; + if (todo === 0) { + callback(); } - ); + }); }); }); }; @@ -741,7 +716,4 @@ ActivityEmitter.on(ActivityConstants.events.DELIVERED_ACTIVITIES, deliveredActiv }); }); -module.exports = { - init, - registerConnection -}; +export { init, registerConnection }; diff --git a/packages/oae-activity/lib/internal/registry.js b/packages/oae-activity/lib/internal/registry.js index 5bb7b40555..fdf34764f2 100644 --- a/packages/oae-activity/lib/internal/registry.js +++ b/packages/oae-activity/lib/internal/registry.js @@ -13,24 +13,22 @@ * permissions and limitations under the License. */ -let _ = require('underscore'); -let util = require('util'); +import util from 'util'; +import _ from 'underscore'; -var activityStreams = {}; -let activityTypes = {}; -let activityEntityTypes = {}; -let activityEntityAssociations = {}; +const activityStreams = {}; +const activityTypes = {}; +const activityEntityTypes = {}; +const activityEntityAssociations = {}; /** * Register an activity stream * * @see ActivityAPI#registerActivityStreamType */ -let registerActivityStreamType = function(activityStreamType, options) { +const registerActivityStreamType = function(activityStreamType, options) { if (activityStreams[activityStreamType]) { - throw new Error( - util.format('Attempted to register duplicate activity stream type "%s"', activityStreamType) - ); + throw new Error(util.format('Attempted to register duplicate activity stream type "%s"', activityStreamType)); } activityStreams[activityStreamType] = options; @@ -41,7 +39,7 @@ let registerActivityStreamType = function(activityStreamType, options) { * * @see ActivityAPI#getRegisteredActivityStreamType */ -let getRegisteredActivityStreamType = function(activityStreamType) { +const getRegisteredActivityStreamType = function(activityStreamType) { return activityStreams[activityStreamType]; }; @@ -50,11 +48,9 @@ let getRegisteredActivityStreamType = function(activityStreamType) { * * @see ActivityAPI#registerActivityType */ -let registerActivityType = function(activityType, options) { +const registerActivityType = function(activityType, options) { if (activityTypes[activityType]) { - throw new Error( - util.format('Attempted to register duplicate activity type of type "%s"', activityType) - ); + throw new Error(util.format('Attempted to register duplicate activity type of type "%s"', activityType)); } if (_.isEmpty(options.streams)) { @@ -72,11 +68,7 @@ let registerActivityType = function(activityType, options) { _.each(options.streams[streamName].router, function(assocations, entityName) { if (_.isEmpty(assocations)) { throw new Error( - util.format( - 'Missing or empty associations for stream "%s" and entity "%s"', - streamName, - entityName - ) + util.format('Missing or empty associations for stream "%s" and entity "%s"', streamName, entityName) ); } }); @@ -91,7 +83,7 @@ let registerActivityType = function(activityType, options) { * @return {Object} All the registered activity types in the system * @see #registerActivityType */ -let getRegisteredActivityTypes = function() { +const getRegisteredActivityTypes = function() { return activityTypes; }; @@ -100,13 +92,10 @@ let getRegisteredActivityTypes = function() { * * @see ActivityAPI#registerActivityEntity */ -let registerActivityEntityType = function(activityEntityType, options) { +const registerActivityEntityType = function(activityEntityType, options) { if (activityEntityTypes[activityEntityType]) { throw new Error( - util.format( - 'Attempted to register duplicate activity entity type of type "%s"', - activityEntityType - ) + util.format('Attempted to register duplicate activity entity type of type "%s"', activityEntityType) ); } @@ -118,7 +107,7 @@ let registerActivityEntityType = function(activityEntityType, options) { * * @return {Object} All the registered activity types in the system */ -let getRegisteredActivityEntityTypes = function() { +const getRegisteredActivityEntityTypes = function() { return activityEntityTypes; }; @@ -127,11 +116,7 @@ let getRegisteredActivityEntityTypes = function() { * * @see ActivityAPI#registerActivityEntityAssociation */ -let registerActivityEntityAssociation = function( - activityEntityType, - associationName, - associationFunction -) { +const registerActivityEntityAssociation = function(activityEntityType, associationName, associationFunction) { if ( activityEntityAssociations[activityEntityType] && activityEntityAssociations[activityEntityType][associationName] @@ -145,8 +130,7 @@ let registerActivityEntityAssociation = function( ); } - activityEntityAssociations[activityEntityType] = - activityEntityAssociations[activityEntityType] || {}; + activityEntityAssociations[activityEntityType] = activityEntityAssociations[activityEntityType] || {}; activityEntityAssociations[activityEntityType][associationName] = associationFunction; }; @@ -155,11 +139,11 @@ let registerActivityEntityAssociation = function( * * @return {Object} All the registered activity entity associations in the system */ -let getRegisteredActivityEntityAssociations = function() { +const getRegisteredActivityEntityAssociations = function() { return activityEntityAssociations; }; -module.exports = { +export { registerActivityStreamType, getRegisteredActivityStreamType, registerActivityType, diff --git a/packages/oae-activity/lib/internal/router.js b/packages/oae-activity/lib/internal/router.js index edfcdc7c61..bfdcaccf5c 100644 --- a/packages/oae-activity/lib/internal/router.js +++ b/packages/oae-activity/lib/internal/router.js @@ -13,22 +13,26 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); - -const AuthzUtil = require('oae-authz/lib/util'); -const log = require('oae-logger').logger('oae-activity-router'); -const Telemetry = require('oae-telemetry').telemetry('activity'); -const TenantsUtil = require('oae-tenants/lib/util'); - -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityModel = require('oae-activity/lib/model'); -const ActivityUtil = require('oae-activity/lib/util'); -const ActivityRegistry = require('./registry'); -const ActivitySystemConfig = require('./config'); -const ActivityEmitter = require('./emitter'); -const ActivityDAO = require('./dao'); -const ActivityBuckets = require('./buckets'); +import util from 'util'; +import _ from 'underscore'; + +import * as AuthzUtil from 'oae-authz/lib/util'; +import { logger } from 'oae-logger'; +import { telemetry } from 'oae-telemetry'; +import * as TenantsUtil from 'oae-tenants/lib/util'; + +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as ActivityModel from 'oae-activity/lib/model'; +import * as ActivityUtil from 'oae-activity/lib/util'; +import ActivityEmitter from './emitter'; +import * as ActivityRegistry from './registry'; +import * as ActivitySystemConfig from './config'; + +import * as ActivityDAO from './dao'; +import * as ActivityBuckets from './buckets'; + +const log = logger('oae-activity-router'); +const Telemetry = telemetry('activity'); /** * Produce, route and queue an activity from an activity seed. @@ -55,22 +59,17 @@ const routeActivity = function(activitySeed, callback) { // Produce all of the activity entities _produceAllEntities(activitySeed, (err, actor, object, target) => { if (err) { - log().error( - { err, activitySeed }, - 'An error occurred when producing the entities for an activity seed' - ); + log().error({ err, activitySeed }, 'An error occurred when producing the entities for an activity seed'); return callback(err); } // Produce the routes from the actor, object and target entities _produceAllRoutes(activitySeed, actor, object, target, (err, routes) => { if (err) { - log().error( - { err, activitySeed }, - 'An error occurred when producing the routes for an activity seed' - ); + log().error({ err, activitySeed }, 'An error occurred when producing the routes for an activity seed'); return callback(err); } + if (_.isEmpty(routes)) { // If no routes were generated then we're done return callback(); @@ -114,10 +113,7 @@ const routeActivity = function(activitySeed, callback) { // Select the activities that are not part of a transient route so they can be stored let activityStreamId = null; if (!route.transient) { - activityStreamId = ActivityUtil.createActivityStreamId( - route.resourceId, - route.streamType - ); + activityStreamId = ActivityUtil.createActivityStreamId(route.resourceId, route.streamType); allDeliveredActivities[activityStreamId] = activity; } @@ -132,10 +128,7 @@ const routeActivity = function(activitySeed, callback) { if (AuthzUtil.isUserId(route.resourceId) && route.resourceId === actor.id) { allRoutedActivities[route.resourceId][route.streamType + '#public'] = activity; if (!route.transient) { - activityStreamId = ActivityUtil.createActivityStreamId( - route.resourceId, - route.streamType + '#public' - ); + activityStreamId = ActivityUtil.createActivityStreamId(route.resourceId, route.streamType + '#public'); allDeliveredActivities[activityStreamId] = activity; } } @@ -143,8 +136,7 @@ const routeActivity = function(activitySeed, callback) { // If a group is involved in a public activity we route the activity to the group's public activity stream const publicGroupEntity = _getGroupEntity(actor, object, target); if (publicGroupEntity) { - allRoutedActivities[publicGroupEntity.id] = - allRoutedActivities[publicGroupEntity.id] || {}; + allRoutedActivities[publicGroupEntity.id] = allRoutedActivities[publicGroupEntity.id] || {}; allRoutedActivities[publicGroupEntity.id][route.streamType + '#public'] = activity; if (!route.transient) { @@ -176,11 +168,8 @@ const routeActivity = function(activitySeed, callback) { // If a group is involved in a loggedin activity we route the activity to the group's loggedin activity stream const loggedinGroupEntity = _getGroupEntity(actor, object, target); if (loggedinGroupEntity) { - allRoutedActivities[loggedinGroupEntity.id] = - allRoutedActivities[loggedinGroupEntity.id] || {}; - allRoutedActivities[loggedinGroupEntity.id][ - route.streamType + '#loggedin' - ] = activity; + allRoutedActivities[loggedinGroupEntity.id] = allRoutedActivities[loggedinGroupEntity.id] || {}; + allRoutedActivities[loggedinGroupEntity.id][route.streamType + '#loggedin'] = activity; if (!route.transient) { activityStreamId = ActivityUtil.createActivityStreamId( @@ -265,8 +254,7 @@ const _produceEntity = function(resource, callback) { } // Find the producer for this resource type. If there isn't one registered, we fall back to the default producer - const activityEntityType = - ActivityRegistry.getRegisteredActivityEntityTypes()[resource.resourceType] || {}; + const activityEntityType = ActivityRegistry.getRegisteredActivityEntityTypes()[resource.resourceType] || {}; const producer = activityEntityType.producer || _defaultActivityEntityProducer; producer(resource, (err, entity) => { @@ -327,37 +315,19 @@ const _produceAllRoutes = function(activitySeed, actor, object, target, callback return callback(null, []); } - const actorRouteConfig = _getRouterConfigForObjectType( - activityConfig, - ActivityConstants.entityTypes.ACTOR - ); - const objectRouteConfig = _getRouterConfigForObjectType( - activityConfig, - ActivityConstants.entityTypes.OBJECT - ); - const targetRouteConfig = _getRouterConfigForObjectType( - activityConfig, - ActivityConstants.entityTypes.TARGET - ); + const actorRouteConfig = _getRouterConfigForObjectType(activityConfig, ActivityConstants.entityTypes.ACTOR); + const objectRouteConfig = _getRouterConfigForObjectType(activityConfig, ActivityConstants.entityTypes.OBJECT); + const targetRouteConfig = _getRouterConfigForObjectType(activityConfig, ActivityConstants.entityTypes.TARGET); // Create the associations context primed for each entity. All these contexts are created with the same associations session, which means access to the same // association for the same entity will not duplicate queries to the database within this routing session const associationsContexts = { - actor: associationsSession.createAssociationsContext( - actor.objectType, - actor[ActivityConstants.properties.OAE_ID] - ), + actor: associationsSession.createAssociationsContext(actor.objectType, actor[ActivityConstants.properties.OAE_ID]), object: object - ? associationsSession.createAssociationsContext( - object.objectType, - object[ActivityConstants.properties.OAE_ID] - ) + ? associationsSession.createAssociationsContext(object.objectType, object[ActivityConstants.properties.OAE_ID]) : null, target: target - ? associationsSession.createAssociationsContext( - target.objectType, - target[ActivityConstants.properties.OAE_ID] - ) + ? associationsSession.createAssociationsContext(target.objectType, target[ActivityConstants.properties.OAE_ID]) : null }; @@ -374,126 +344,112 @@ const _produceAllRoutes = function(activitySeed, actor, object, target, callback } // Route the target entity - _produceRoutes( - associationsContexts.target, - target, - targetRouteConfig, - (err, targetRoutes) => { + _produceRoutes(associationsContexts.target, target, targetRouteConfig, (err, targetRoutes) => { + if (err) { + return callback(err); + } + + // Determine the propagation rules for the actor + _producePropagation(associationsContexts.actor, actor, (err, actorPropagationRules) => { if (err) { return callback(err); } - // Determine the propagation rules for the actor - _producePropagation(associationsContexts.actor, actor, (err, actorPropagationRules) => { + // Determine the propagation rules for the object + _producePropagation(associationsContexts.object, object, (err, objectPropagationRules) => { if (err) { return callback(err); } - // Determine the propagation rules for the object - _producePropagation( - associationsContexts.object, - object, - (err, objectPropagationRules) => { - if (err) { - return callback(err); - } + // Determine the propagation rules for the target + _producePropagation(associationsContexts.target, target, (err, targetPropagationRules) => { + if (err) { + return callback(err); + } - // Determine the propagation rules for the target - _producePropagation( - associationsContexts.target, - target, - (err, targetPropagationRules) => { - if (err) { - return callback(err); - } + // The list of routes unioned represents all the routes the activity "wants" to be routed to. We will + // need to further filter this list down to those that the propagation rules specify it is "allowed" to + // be routed to based on the individual entity propagation rules + const allRoutes = _.chain(actorRoutes) + .union(objectRoutes, targetRoutes) + .compact() + .filter(route => { + if ( + route.streamType === ActivityConstants.streams.NOTIFICATION || + route.streamType === ActivityConstants.streams.EMAIL + ) { + // If this is a notification or email route, we can only accept users other than the actor + const isPrincipalRoute = + AuthzUtil.isUserId(route.resourceId) || AuthzUtil.isEmail(route.resourceId); + return isPrincipalRoute && route.resourceId !== actor[ActivityConstants.properties.OAE_ID]; + } - // The list of routes unioned represents all the routes the activity "wants" to be routed to. We will - // need to further filter this list down to those that the propagation rules specify it is "allowed" to - // be routed to based on the individual entity propagation rules - const allRoutes = _.chain(actorRoutes) - .union(objectRoutes, targetRoutes) - .compact() - .filter(route => { - if ( - route.streamType === ActivityConstants.streams.NOTIFICATION || - route.streamType === ActivityConstants.streams.EMAIL - ) { - // If this is a notification or email route, we can only accept users other than the actor - const isPrincipalRoute = - AuthzUtil.isUserId(route.resourceId) || - AuthzUtil.isEmail(route.resourceId); - return ( - isPrincipalRoute && - route.resourceId !== actor[ActivityConstants.properties.OAE_ID] - ); - } - // This route is not a notification or email, we need to include it so we can apply propagation on it - return true; - }) - .value(); - - // First filter down the routes by the actor propagation - _applyPropagations( - ActivityConstants.entityTypes.ACTOR, - actorPropagationRules, - allRoutes, - associationsContexts, - actor, - actorRoutes, - (err, includedRoutes) => { - if (err) { - return callback(err); - } - if (_.isEmpty(includedRoutes)) { - // No routes survived the actor propagation, simply return the empty array - return callback(null, []); - } + // This route is not a notification or email, we need to include it so we can apply propagation on it + return true; + }) + .value(); + + // First filter down the routes by the actor propagation + _applyPropagations( + ActivityConstants.entityTypes.ACTOR, + actorPropagationRules, + allRoutes, + associationsContexts, + actor, + actorRoutes, + (err, includedRoutes) => { + if (err) { + return callback(err); + } - // Further filter routes down by object propagation - _applyPropagations( - ActivityConstants.entityTypes.OBJECT, - objectPropagationRules, - includedRoutes, - associationsContexts, - object, - objectRoutes, - (err, includedRoutes) => { - if (err) { - return callback(err); - } - if (_.isEmpty(includedRoutes)) { - // No routes survived the object propagation, simply return the empty array - return callback(null, []); - } - - // Further filter routes down by target propagation - _applyPropagations( - ActivityConstants.entityTypes.TARGET, - targetPropagationRules, - includedRoutes, - associationsContexts, - target, - targetRoutes, - (err, includedRoutes) => { - if (err) { - return callback(err); - } - - // Return the final list of routes - return callback(null, includedRoutes); - } - ); - } - ); - } - ); + if (_.isEmpty(includedRoutes)) { + // No routes survived the actor propagation, simply return the empty array + return callback(null, []); } - ); - } - ); + + // Further filter routes down by object propagation + _applyPropagations( + ActivityConstants.entityTypes.OBJECT, + objectPropagationRules, + includedRoutes, + associationsContexts, + object, + objectRoutes, + (err, includedRoutes) => { + if (err) { + return callback(err); + } + + if (_.isEmpty(includedRoutes)) { + // No routes survived the object propagation, simply return the empty array + return callback(null, []); + } + + // Further filter routes down by target propagation + _applyPropagations( + ActivityConstants.entityTypes.TARGET, + targetPropagationRules, + includedRoutes, + associationsContexts, + target, + targetRoutes, + (err, includedRoutes) => { + if (err) { + return callback(err); + } + + // Return the final list of routes + return callback(null, includedRoutes); + } + ); + } + ); + } + ); + }); }); - } - ); + }); + }); }); }); }; @@ -547,9 +503,11 @@ const _getGroupEntity = function(actor, object, target) { if (object && AuthzUtil.isGroupId(object.id)) { return object; } + if (target && AuthzUtil.isGroupId(target.id)) { return target; } + return null; }; @@ -606,10 +564,7 @@ const _applyPropagations = function( entityRoutes, (err, includeRoutes, excludeRoutes) => { if (err) { - log().warn( - { err, propagation, entity }, - 'There was an error applying a propagation rule for an entity' - ); + log().warn({ err, propagation, entity }, 'There was an error applying a propagation rule for an entity'); } else { _includeRoutes = _.union(_includeRoutes, includeRoutes); _excludeRoutes = excludeRoutes; @@ -620,6 +575,7 @@ const _applyPropagations = function( // rules in this chain as we've already included them all return callback(null, _includeRoutes, _excludeRoutes); } + // There are still more routes to try and include. Continue shifting through the propagation rules chain return _applyPropagations( objectType, @@ -672,21 +628,18 @@ const _applyPropagation = function( const excludedRoutes = []; /*! - * Apply an association to the given routes, populating the `includedRoutes` and `excludedRoutes` arrays with routes from - * the `routes` array according to who the association determines can and cannot receive the entity - * - * @param {AssociationsContext} associationsCtx The associations context from which to apply the specified association - * @param {String} associationName The name of the association to apply - * @param {Function} callback Standard callback function - * @param {Error} callback.err An error that occurred, if any - */ + * Apply an association to the given routes, populating the `includedRoutes` and `excludedRoutes` arrays with routes from + * the `routes` array according to who the association determines can and cannot receive the entity + * + * @param {AssociationsContext} associationsCtx The associations context from which to apply the specified association + * @param {String} associationName The name of the association to apply + * @param {Function} callback Standard callback function + * @param {Error} callback.err An error that occurred, if any + */ const _applyAssociationsCtx = function(associationsCtx, associationName, callback) { associationsCtx.get(associationName, (err, associations) => { if (err) { - log().warn( - { err, association: associationName }, - 'An error occurred while applying associations' - ); + log().warn({ err, association: associationName }, 'An error occurred while applying associations'); } // Index the entity associations into a hash to make the next loop O(n) instead of O(n^2) @@ -712,6 +665,7 @@ const _applyPropagation = function( // Include all the routes return callback(null, routes, []); } + if (propagation.type === ActivityConstants.entityPropagation.TENANT) { // Split the routes into those in the same tenant, and those in other tenants _.each(routes, route => { @@ -725,6 +679,7 @@ const _applyPropagation = function( return callback(null, includedRoutes, excludedRoutes); } + if (propagation.type === ActivityConstants.entityPropagation.INTERACTING_TENANTS) { // Split the routes into those who can interact with the entity's tenant, and those who cannot _.each(routes, route => { @@ -738,6 +693,7 @@ const _applyPropagation = function( return callback(null, includedRoutes, excludedRoutes); } + if (propagation.type === ActivityConstants.entityPropagation.ROUTES) { // Index the entity routes into a hash to make the next loop O(n) instead of O(n^2) const entityRoutesHash = {}; @@ -756,6 +712,7 @@ const _applyPropagation = function( return callback(null, includedRoutes, excludedRoutes); } + if (propagation.type === ActivityConstants.entityPropagation.SELF) { // Apply the "self" association of the item _applyAssociationsCtx(associationsContexts[objectType], 'self', err => { @@ -776,26 +733,26 @@ const _applyPropagation = function( }); } else if (propagation.type === ActivityConstants.entityPropagation.EXTERNAL_ASSOCIATION) { /*! - * Apply a specified association that is of an entity that is external to the item itself. This is useful - * in situations where an item you have access to has been impacted by an item that you do not have access - * to in an activity. It is useful for you to still receive the activity, even though more basic propagation - * may indicate that you don't have access. For example: - * - * * UserA is the manager of ContentA, but not a member of private GroupB - * * UserB adds ContentA to the library of GroupB - * * This generates activity "content-share" with an {actor, object, target} of {UserB, ContentA, GroupB} - * * UserA should receive this activity since they are a manager of ContentA, but theoretically they don't - * have access to GroupB - * - * For this case, we actually do want to deliver the activity to UserA's activity feed. This is not necessarily - * a privacy breach, because UserA is now able to discover GroupB VIA the members list of ContentA. To express - * this relationship, EXTERNAL_ASSOCATION can be used on groups to indicate: - * - * "The 'managers' association of the 'object' entity of an activity is allowed to receive the group as an activity" - * - * Thus, the "managers" of ContentA will be in the propagation list of GroupB since ContentA is the "object" entity - * of the activity. - */ + * Apply a specified association that is of an entity that is external to the item itself. This is useful + * in situations where an item you have access to has been impacted by an item that you do not have access + * to in an activity. It is useful for you to still receive the activity, even though more basic propagation + * may indicate that you don't have access. For example: + * + * * UserA is the manager of ContentA, but not a member of private GroupB + * * UserB adds ContentA to the library of GroupB + * * This generates activity "content-share" with an {actor, object, target} of {UserB, ContentA, GroupB} + * * UserA should receive this activity since they are a manager of ContentA, but theoretically they don't + * have access to GroupB + * + * For this case, we actually do want to deliver the activity to UserA's activity feed. This is not necessarily + * a privacy breach, because UserA is now able to discover GroupB VIA the members list of ContentA. To express + * this relationship, EXTERNAL_ASSOCATION can be used on groups to indicate: + * + * "The 'managers' association of the 'object' entity of an activity is allowed to receive the group as an activity" + * + * Thus, the "managers" of ContentA will be in the propagation list of GroupB since ContentA is the "object" entity + * of the activity. + */ const externalAssociationsCtx = associationsContexts[propagation.objectType]; if (!externalAssociationsCtx) { @@ -811,9 +768,7 @@ const _applyPropagation = function( return callback(null, includedRoutes, excludedRoutes); }); } else { - return callback( - new Error(util.format('Received invalid propagation type "%s"', propagation.type)) - ); + return callback(new Error(util.format('Received invalid propagation type "%s"', propagation.type))); } }; @@ -859,8 +814,8 @@ const _produceRoutes = function(associationsCtx, entity, routeConfig, callback) const routes = []; /*! - * Recursively produce the routes for the activity entity based on the routing configuration of the activity that is applied to this entity - */ + * Recursively produce the routes for the activity entity based on the routing configuration of the activity that is applied to this entity + */ const routeStreamAssociations = function() { if (_.isEmpty(routeConfig)) { log().trace({ entity, routes }, 'Generated routes for activity entity'); @@ -925,10 +880,7 @@ const _routeAssociations = function(associationsCtx, associationNames, callback, if (err) { log().warn({ err }, 'Error fetching association "%s"', associationName); } else if (associations && !_.isArray(associations)) { - log().warn( - 'Ignoring using association "%s" as a route which returns a non-array object', - associationName - ); + log().warn('Ignoring using association "%s" as a route which returns a non-array object', associationName); } else if (associations) { if (exclusionAssociation) { // An exclusion association removes routes from the aggregated result set @@ -1005,10 +957,7 @@ const _queueActivities = function(activitySeed, routedActivities, callback) { // Queue the aggregates into their associated buckets to indicate they need to be collected and delivered ActivityDAO.saveQueuedActivities(activityBuckets, err => { if (err) { - log().error( - { err, activitySeed }, - 'Could not save the queued activities for an activity seed' - ); + log().error({ err, activitySeed }, 'Could not save the queued activities for an activity seed'); return callback(err); } @@ -1054,6 +1003,4 @@ const _getBucketNumber = function(route, activitySeed) { return ActivityBuckets.getBucketNumber(route + activitySeed.activityType, numberOfBuckets); }; -module.exports = { - routeActivity -}; +export { routeActivity }; diff --git a/packages/oae-activity/lib/internal/transformer.js b/packages/oae-activity/lib/internal/transformer.js index 1a39b94f77..25d02d5ed7 100644 --- a/packages/oae-activity/lib/internal/transformer.js +++ b/packages/oae-activity/lib/internal/transformer.js @@ -13,14 +13,17 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const log = require('oae-logger').logger('oae-activity-push'); -const TenantsAPI = require('oae-tenants'); +import * as AuthzUtil from 'oae-authz/lib/util'; -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityRegistry = require('./registry'); +import { logger } from 'oae-logger'; +import * as TenantsAPI from 'oae-tenants'; + +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as ActivityRegistry from './registry'; + +const log = logger('oae-activity-push'); /** * Given an array of persistent activities from a stream, convert them into activities suitable to be delivered to the UI. @@ -54,24 +57,23 @@ const transformActivities = function(ctx, activities, transformerType, callback) }; }); - log().error( - { error, activities: logActivities, user: ctx.user() }, - 'Failed to get activity entities' - ); + log().error({ error, activities: logActivities, user: ctx.user() }, 'Failed to get activity entities'); throw error; } + const objectTypes = _.keys(activityEntitiesByObjectType); let errOccurred = null; let numProcessed = 0; /*! - * Handles the callback for when a set of entities for an object type have been transformed. - */ + * Handles the callback for when a set of entities for an object type have been transformed. + */ const _handleTransform = function(err, objectType, transformedActivityEntities) { if (errOccurred) { // Do nothing because we've already err'd return; } + if (err) { errOccurred = err; return callback(err); @@ -91,42 +93,31 @@ const transformActivities = function(ctx, activities, transformerType, callback) if (objectTypes.length > 0) { objectTypes.forEach(objectType => { const transformer = _getEntityTypeTransformer(objectType, transformerType); - transformer( - ctx, - activityEntitiesByObjectType[objectType], - (err, transformedActivityEntities) => { - if (err) { - return callback(err); - } - - // Ensure all transformed entities have at least the objectType and the oae:id - _.keys(transformedActivityEntities).forEach(activityId => { - _.keys(transformedActivityEntities[activityId]).forEach(entityId => { - if (!transformedActivityEntities[activityId][entityId].objectType) { - transformedActivityEntities[activityId][entityId].objectType = objectType; - } - if ( - !transformedActivityEntities[activityId][entityId][ - ActivityConstants.properties.OAE_ID - ] - ) { - transformedActivityEntities[activityId][entityId][ - ActivityConstants.properties.OAE_ID - ] = entityId; - } - - // We only need the tenant information when transforming the entities into ActivityStrea.ms compliant entities - if (transformerType === ActivityConstants.transformerTypes.ACTIVITYSTREAMS) { - _addTenantInformationToActivityEntity( - transformedActivityEntities[activityId][entityId] - ); - } - }); + transformer(ctx, activityEntitiesByObjectType[objectType], (err, transformedActivityEntities) => { + if (err) { + return callback(err); + } + + // Ensure all transformed entities have at least the objectType and the oae:id + _.keys(transformedActivityEntities).forEach(activityId => { + _.keys(transformedActivityEntities[activityId]).forEach(entityId => { + if (!transformedActivityEntities[activityId][entityId].objectType) { + transformedActivityEntities[activityId][entityId].objectType = objectType; + } + + if (!transformedActivityEntities[activityId][entityId][ActivityConstants.properties.OAE_ID]) { + transformedActivityEntities[activityId][entityId][ActivityConstants.properties.OAE_ID] = entityId; + } + + // We only need the tenant information when transforming the entities into ActivityStrea.ms compliant entities + if (transformerType === ActivityConstants.transformerTypes.ACTIVITYSTREAMS) { + _addTenantInformationToActivityEntity(transformedActivityEntities[activityId][entityId]); + } }); + }); - return _handleTransform(err, objectType, transformedActivityEntities); - } - ); + return _handleTransform(err, objectType, transformedActivityEntities); + }); }); } else { return callback(); @@ -147,9 +138,11 @@ const _getEntityTypeTransformer = function(objectType, transformerType) { if (_.isObject(transformer) && _.isFunction(transformer[transformerType])) { return transformer[transformerType]; } + if (_.isFunction(transformer)) { return transformer; } + return _defaultActivityEntityTransformer; }; @@ -200,29 +193,19 @@ const _getEntityTypeTransformer = function(objectType, transformerType) { * @param {Object} activityEntitiesByObjectType An object of: objectType -> activityId -> entityId -> persistentEntity that holds the categorized entities of all the activities in a stream request * @api private */ -const _getActivityEntitiesByObjectType = function( - activityId, - entity, - activityEntitiesByObjectType -) { +const _getActivityEntitiesByObjectType = function(activityId, entity, activityEntitiesByObjectType) { if (entity && entity.objectType !== 'collection') { - activityEntitiesByObjectType[entity.objectType] = - activityEntitiesByObjectType[entity.objectType] || {}; + activityEntitiesByObjectType[entity.objectType] = activityEntitiesByObjectType[entity.objectType] || {}; activityEntitiesByObjectType[entity.objectType][activityId] = activityEntitiesByObjectType[entity.objectType][activityId] || {}; - activityEntitiesByObjectType[entity.objectType][activityId][ - entity[ActivityConstants.properties.OAE_ID] - ] = entity; + activityEntitiesByObjectType[entity.objectType][activityId][entity[ActivityConstants.properties.OAE_ID]] = entity; } else if (entity) { // This is actually a collection of more entities. Iterate and collect them. entity[ActivityConstants.properties.OAE_COLLECTION].forEach(entity => { - activityEntitiesByObjectType[entity.objectType] = - activityEntitiesByObjectType[entity.objectType] || {}; + activityEntitiesByObjectType[entity.objectType] = activityEntitiesByObjectType[entity.objectType] || {}; activityEntitiesByObjectType[entity.objectType][activityId] = activityEntitiesByObjectType[entity.objectType][activityId] || {}; - activityEntitiesByObjectType[entity.objectType][activityId][ - entity[ActivityConstants.properties.OAE_ID] - ] = entity; + activityEntitiesByObjectType[entity.objectType][activityId][entity[ActivityConstants.properties.OAE_ID]] = entity; }); } }; @@ -238,21 +221,9 @@ const _getActivityEntitiesByObjectType = function( const _transformActivities = function(transformedActivityEntitiesByObjectType, activities) { activities.forEach(activity => { const activityId = activity[ActivityConstants.properties.OAE_ACTIVITY_ID]; - activity.actor = _transformEntity( - transformedActivityEntitiesByObjectType, - activityId, - activity.actor - ); - activity.object = _transformEntity( - transformedActivityEntitiesByObjectType, - activityId, - activity.object - ); - activity.target = _transformEntity( - transformedActivityEntitiesByObjectType, - activityId, - activity.target - ); + activity.actor = _transformEntity(transformedActivityEntitiesByObjectType, activityId, activity.actor); + activity.object = _transformEntity(transformedActivityEntitiesByObjectType, activityId, activity.object); + activity.target = _transformEntity(transformedActivityEntitiesByObjectType, activityId, activity.target); }); }; @@ -273,13 +244,10 @@ const _transformEntity = function(transformedActivityEntitiesByObjectType, activ if (entity.objectType !== 'collection') { return transformedActivityEntitiesByObjectType[entity.objectType][activityId][entityId]; } + const transformedCollection = []; entity[ActivityConstants.properties.OAE_COLLECTION].forEach(collectionEntity => { - const transformedEntity = _transformEntity( - transformedActivityEntitiesByObjectType, - activityId, - collectionEntity - ); + const transformedEntity = _transformEntity(transformedActivityEntitiesByObjectType, activityId, collectionEntity); if (transformedEntity) { transformedCollection.push(transformedEntity); } @@ -300,11 +268,7 @@ const _defaultActivityEntityTransformer = function(ctx, activityEntities, callba transformedEntities[activityId] = transformedEntities[activityId] || {}; _.each(entities, (entity, entityKey) => { // Pick just the objectType and the oae:id of the entity for the transformed entity. - transformedEntities[activityId][entityKey] = _.pick( - entity, - 'objectType', - ActivityConstants.properties.OAE_ID - ); + transformedEntities[activityId][entityKey] = _.pick(entity, 'objectType', ActivityConstants.properties.OAE_ID); }); }); @@ -322,9 +286,7 @@ const _addTenantInformationToActivityEntity = function(entity) { if (entity) { // If the entity is a single entity, apply the tenant information if (entity[ActivityConstants.properties.OAE_ID]) { - const { tenantAlias } = AuthzUtil.getResourceFromId( - entity[ActivityConstants.properties.OAE_ID] - ); + const { tenantAlias } = AuthzUtil.getResourceFromId(entity[ActivityConstants.properties.OAE_ID]); const tenant = TenantsAPI.getTenant(tenantAlias); if (tenant) { entity[ActivityConstants.properties.OAE_TENANT] = tenant.compact(); @@ -332,12 +294,9 @@ const _addTenantInformationToActivityEntity = function(entity) { // If the entity is a collection of entities, iterate over each one and apply the tenant information } else if (entity[ActivityConstants.properties.OAE_COLLECTION]) { - _.each( - entity[ActivityConstants.properties.OAE_COLLECTION], - _addTenantInformationToActivityEntity - ); + _.each(entity[ActivityConstants.properties.OAE_COLLECTION], _addTenantInformationToActivityEntity); } } }; -module.exports = { transformActivities }; +export { transformActivities }; diff --git a/packages/oae-activity/lib/migration.js b/packages/oae-activity/lib/migration.js index 92746a7e18..1a3326e0b4 100644 --- a/packages/oae-activity/lib/migration.js +++ b/packages/oae-activity/lib/migration.js @@ -1,4 +1,4 @@ -const Cassandra = require('oae-util/lib/cassandra'); +import { createColumnFamilies } from 'oae-util/lib/cassandra'; /** * Ensure that the all of the activity-related schemas are created. If they already exist, this method will not do anything. @@ -8,7 +8,7 @@ const Cassandra = require('oae-util/lib/cassandra'); * @api private */ const ensureSchema = function(callback) { - Cassandra.createColumnFamilies( + createColumnFamilies( { ActivityStreams: 'CREATE TABLE "ActivityStreams" ("activityStreamId" text, "activityId" text, "activity" text, PRIMARY KEY ("activityStreamId", "activityId")) WITH COMPACT STORAGE', @@ -18,4 +18,4 @@ const ensureSchema = function(callback) { ); }; -module.exports = { ensureSchema }; +export { ensureSchema }; diff --git a/packages/oae-activity/lib/model.js b/packages/oae-activity/lib/model.js index b11b3b550a..ac9c0537c4 100644 --- a/packages/oae-activity/lib/model.js +++ b/packages/oae-activity/lib/model.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const clone = require('clone'); +import _ from 'underscore'; +import clone from 'clone'; -const { ActivityConstants } = require('oae-activity/lib/constants'); +import { ActivityConstants } from 'oae-activity/lib/constants'; /// ///////////// // SEED MODEL // @@ -48,11 +48,7 @@ const ActivitySeedResource = function(resourceType, resourceId, resourceData) { * @return {ActivitySeedResource} The activity seed resource, as described in the summary */ ActivitySeedResource.fromResource = function(resource) { - return new ActivitySeedResource( - resource.resourceType, - resource.id, - _.object([[resource.resourceType, resource]]) - ); + return new ActivitySeedResource(resource.resourceType, resource.id, _.object([[resource.resourceType, resource]])); }; /** @@ -66,14 +62,7 @@ ActivitySeedResource.fromResource = function(resource) { * @param {ActivitySeedResource} [objectResource] The Object on which the activity was performed * @param {ActivitySeedResource} [targetResource] The Target resource of the activity, as recommended in the ActivityStrea.ms specification */ -const ActivitySeed = function( - activityType, - published, - verb, - actorResource, - objectResource, - targetResource -) { +const ActivitySeed = function(activityType, published, verb, actorResource, objectResource, targetResource) { const that = {}; that.activityType = activityType; that.published = published; @@ -272,6 +261,7 @@ const AssociationsSession = function(registeredAssociations, actor, object, targ if (err) { return callback(err); } + if (!association) { // A successful but falsey association is simply treated as an empty result, as it cannot be confused with // the association (or context entity) not existing @@ -300,25 +290,23 @@ const AssociationsSession = function(registeredAssociations, actor, object, targ const associationsContext = {}; /*! - * Get the parent associations session for this associations context - * - * @return {AssociationsSession} The parent associationsSession - */ + * Get the parent associations session for this associations context + * + * @return {AssociationsSession} The parent associationsSession + */ associationsContext.getSession = function() { return associationsSession; }; /*! - * A method that can be used to fetch the associations of the entity in context - * - * @param {String} associationName The name of the association to fetch - * @param {Object} callback.err An error that occurred, if any - * @param {Object} callback.association The association. Usually a list of strings indicating ids, however can really be any value. Note that it cannot be used for routing purposes if it does not return an array of strings - */ + * A method that can be used to fetch the associations of the entity in context + * + * @param {String} associationName The name of the association to fetch + * @param {Object} callback.err An error that occurred, if any + * @param {Object} callback.association The association. Usually a list of strings indicating ids, however can really be any value. Note that it cannot be used for routing purposes if it does not return an array of strings + */ associationsContext.get = function(associationName, callback) { - return associationsContext - .getSession() - .getByEntityId(entityType, entityId, associationName, callback); + return associationsContext.getSession().getByEntityId(entityType, entityId, associationName, callback); }; return associationsContext; @@ -327,7 +315,7 @@ const AssociationsSession = function(registeredAssociations, actor, object, targ return associationsSession; }; -module.exports = { +export { ActivitySeedResource, ActivitySeed, ActivityMediaLink, diff --git a/packages/oae-activity/lib/rest.js b/packages/oae-activity/lib/rest.js index 56edec9949..3577da82ef 100644 --- a/packages/oae-activity/lib/rest.js +++ b/packages/oae-activity/lib/rest.js @@ -13,13 +13,13 @@ * permissions and limitations under the License. */ -const sockjs = require('sockjs'); +import sockjs from 'sockjs'; -const OAE = require('oae-util/lib/oae'); -const OaeUtil = require('oae-util/lib/util'); +import * as OAE from 'oae-util/lib/oae'; +import * as OaeUtil from 'oae-util/lib/util'; -const ActivityAPI = require('oae-activity'); -const ActivityPush = require('./internal/push'); +import * as ActivityAPI from 'oae-activity'; +import * as ActivityPush from './internal/push'; /// /////////////////// // ACTIVITY STREAMS // @@ -37,20 +37,13 @@ const ActivityPush = require('./internal/push'); const _handleGetActivities = function(resourceId, req, res) { const limit = OaeUtil.getNumberParam(req.query.limit, 10, 1, 25); const { start } = req.query; - ActivityAPI.getActivityStream( - req.ctx, - resourceId, - start, - limit, - req.query.format, - (err, activityStream) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - res.status(200).send(activityStream); + ActivityAPI.getActivityStream(req.ctx, resourceId, start, limit, req.query.format, (err, activityStream) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(200).send(activityStream); + }); }; /** diff --git a/packages/oae-activity/lib/test/util.js b/packages/oae-activity/lib/test/util.js index bd30e177b7..8cfd30df1c 100644 --- a/packages/oae-activity/lib/test/util.js +++ b/packages/oae-activity/lib/test/util.js @@ -15,20 +15,20 @@ /* eslint-disable no-unused-vars */ /* eslint-disable max-params */ -const assert = require('assert'); -const _ = require('underscore'); -const cheerio = require('cheerio'); -const ShortId = require('shortid'); -const sjsc = require('sockjs-client-ws'); +import assert from 'assert'; +import _ from 'underscore'; +import cheerio from 'cheerio'; +import ShortId from 'shortid'; +import sjsc from 'sockjs-client-ws'; -const EmitterAPI = require('oae-emitter'); -const MqTestsUtil = require('oae-util/lib/test/mq-util'); -const OaeUtil = require('oae-util/lib/util'); -const RestAPI = require('oae-rest'); +import * as EmitterAPI from 'oae-emitter'; +import * as MqTestsUtil from 'oae-util/lib/test/mq-util'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as RestAPI from 'oae-rest'; -const ActivityAggregator = require('oae-activity/lib/internal/aggregator'); -const ActivityAPI = require('oae-activity'); -const { ActivityConstants } = require('oae-activity/lib/constants'); +import * as ActivityAggregator from 'oae-activity/lib/internal/aggregator'; +import * as ActivityAPI from 'oae-activity'; +import { ActivityConstants } from 'oae-activity/lib/constants'; /** * Refresh the activity module's configuration, keeping in mind default test configuration. This is @@ -178,12 +178,7 @@ const assertFeedContainsActivity = function( * @param {Function} callback Standard callback function * @throws {Error} An assertion error gets thrown if the activity was found */ -const assertFeedDoesNotContainActivity = function( - restContext, - activityStreamId, - activityType, - callback -) { +const assertFeedDoesNotContainActivity = function(restContext, activityStreamId, activityType, callback) { collectAndGetActivityStream(restContext, activityStreamId, null, (err, response) => { assert.ok(!err); const activity = _.findWhere(response.items, { 'oae:activityType': activityType }); @@ -232,11 +227,7 @@ const _assertNotificationStreamContainsActivity = function( * @param {Function} callback Standard callback function * @throws {Error} An assertion error gets thrown if the activity was found */ -const _assertNotificationStreamDoesNotContainActivity = function( - restContext, - activityType, - callback -) { +const _assertNotificationStreamDoesNotContainActivity = function(restContext, activityType, callback) { collectAndGetNotificationStream(restContext, null, (err, notificationStream) => { assert.ok(!err); const activity = _.findWhere(notificationStream.items, { 'oae:activityType': activityType }); @@ -255,14 +246,7 @@ const _assertNotificationStreamDoesNotContainActivity = function( * @param {String|String[]} [objectEntityId] The id of the entity that should be the object, or an array of expected object entity ids if the entity is expected to be an oae:collection aggregate. If not specified, an assertion will be performed that the object does not exist * @param {String|String[]} [targetEntityId] The id of the entity taht should be the target, or an array of expected target entity ids if the entity is expected to be an oae:collection aggregate. If not specified, an assertion will be performed that the target does not exist */ -const assertActivity = function( - activity, - activityType, - verb, - actorEntityId, - objectEntityId, - targetEntityId -) { +const assertActivity = function(activity, activityType, verb, actorEntityId, objectEntityId, targetEntityId) { assert.ok(activity); assert.strictEqual(activity[ActivityConstants.properties.OAE_ACTIVITY_TYPE], activityType); assert.strictEqual(activity.verb, verb); @@ -292,10 +276,7 @@ const _assertActivityEntity = function(activityEntity, entityId) { // Ensure it is a collection with the same amount of ids as the given list of entity ids assert.strictEqual(activityEntity.objectType, 'collection'); assert.ok(activityEntity[ActivityConstants.properties.OAE_COLLECTION]); - assert.strictEqual( - activityEntity[ActivityConstants.properties.OAE_COLLECTION].length, - entityIds.length - ); + assert.strictEqual(activityEntity[ActivityConstants.properties.OAE_COLLECTION].length, entityIds.length); // Ensure every id in the list is in the entity collection _.each(activityEntity[ActivityConstants.properties.OAE_COLLECTION], activityEntity => { @@ -527,16 +508,10 @@ const getFullySetupPushClient = function(data, callback) { const allRegisteredCallback = _.after(data.streams.length, callback); _.each(data.streams, stream => { - client.subscribe( - stream.resourceId, - stream.streamType, - stream.token, - stream.format, - err => { - assert.ok(!err, 'Failed to register for feed'); - allRegisteredCallback(client); - } - ); + client.subscribe(stream.resourceId, stream.streamType, stream.token, stream.format, err => { + assert.ok(!err, 'Failed to register for feed'); + allRegisteredCallback(client); + }); }); } ); @@ -555,38 +530,35 @@ const getFullySetupPushClient = function(data, callback) { * @param {Function} callback Invoked when a message with the specified activity has been received * @param {Activity} callback.activity The activity object that was contained in the message */ -const waitForPushActivity = function( - client, - activityType, - verb, - actorId, - objectId, - targetId, - callback -) { +const waitForPushActivity = function(client, activityType, verb, actorId, objectId, targetId, callback) { /*! - * Listener function to wait for messages, perform the activity filter, and unbind itself from - * the client when the activity has been found. When found, the callback will be invoked with - * the activity - */ + * Listener function to wait for messages, perform the activity filter, and unbind itself from + * the client when the activity has been found. When found, the callback will be invoked with + * the activity + */ const _onMessage = function(message) { const targetActivity = _.find(message.activities, activity => { if (activity[ActivityConstants.properties.OAE_ACTIVITY_TYPE] !== activityType) { return false; } + if (activity.verb !== verb) { return false; } + if (activity.actor[ActivityConstants.properties.OAE_ID] !== actorId) { return false; } + if (objectId && activity.object[ActivityConstants.properties.OAE_ID] !== objectId) { return false; } + if (targetId) { if (!activity.target) { return false; } + if (activity.target[ActivityConstants.properties.OAE_ID] !== targetId) { return false; } @@ -605,7 +577,7 @@ const waitForPushActivity = function( return client.on('message', _onMessage); }; -module.exports = { +export { waitForPushActivity, getFullySetupPushClient, refreshConfiguration, @@ -615,8 +587,8 @@ module.exports = { markNotificationsAsRead, assertFeedContainsActivity, assertFeedDoesNotContainActivity, - assertNotificationStreamContainsActivity: _assertNotificationStreamContainsActivity, - assertNotificationStreamDoesNotContainActivity: _assertNotificationStreamDoesNotContainActivity, + _assertNotificationStreamContainsActivity as assertNotificationStreamContainsActivity, + _assertNotificationStreamDoesNotContainActivity as assertNotificationStreamDoesNotContainActivity, assertActivity, parseActivityHtml, getPushClient diff --git a/packages/oae-activity/lib/util.js b/packages/oae-activity/lib/util.js index d36c119fdc..94fcde9620 100644 --- a/packages/oae-activity/lib/util.js +++ b/packages/oae-activity/lib/util.js @@ -13,14 +13,14 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const AuthzAPI = require('oae-authz'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzUtil = require('oae-authz/lib/util'); +import * as AuthzAPI from 'oae-authz'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as AuthzUtil from 'oae-authz/lib/util'; -const { ActivityConstants } = require('oae-activity/lib/constants'); +import { ActivityConstants } from 'oae-activity/lib/constants'; /** * Get a propagation specification that is standard for a resource. This effectively assumes that a resource does not get @@ -241,9 +241,4 @@ const parseActivityStreamId = function(activityStreamId) { }; }; -module.exports = { - getStandardResourcePropagation, - getAllAuthzMembersByRole, - createActivityStreamId, - parseActivityStreamId -}; +export { getStandardResourcePropagation, getAllAuthzMembersByRole, createActivityStreamId, parseActivityStreamId }; diff --git a/packages/oae-activity/tests/test-activity.js b/packages/oae-activity/tests/test-activity.js index 132090954a..3f62979833 100644 --- a/packages/oae-activity/tests/test-activity.js +++ b/packages/oae-activity/tests/test-activity.js @@ -15,25 +15,23 @@ /* eslint-disable no-unused-vars */ /* eslint-disable max-nested-callbacks */ -const assert = require('assert'); -const util = require('util'); -const _ = require('underscore'); - -const ConfigTestsUtil = require('oae-config/lib/test/util'); -const { Context } = require('oae-context/lib/api'); -const FollowingTestsUtil = require('oae-following/lib/test/util'); -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); - -const ActivityAggregator = require('oae-activity/lib/internal/aggregator'); -const ActivityAPI = require('oae-activity'); -const ActivityDAO = require('oae-activity/lib/internal/dao'); -const ActivityRegistry = require('oae-activity/lib/internal/registry'); -const { ActivitySeed } = require('oae-activity/lib/model'); -const { ActivitySeedResource } = require('oae-activity/lib/model'); -const ActivityTestUtil = require('oae-activity/lib/test/util'); -const ActivityUtil = require('oae-activity/lib/util'); -const { AssociationsSession } = require('oae-activity/lib/model'); +import assert from 'assert'; +import util from 'util'; +import _ from 'underscore'; +import * as ConfigTestsUtil from 'oae-config/lib/test/util'; +import { Context } from 'oae-context/lib/api'; +import * as FollowingTestsUtil from 'oae-following/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as ActivityAggregator from 'oae-activity/lib/internal/aggregator'; +import * as ActivityAPI from 'oae-activity'; +import * as ActivityDAO from 'oae-activity/lib/internal/dao'; +import * as ActivityRegistry from 'oae-activity/lib/internal/registry'; +import { ActivitySeed } from 'oae-activity/lib/model'; +import { ActivitySeedResource } from 'oae-activity/lib/model'; +import * as ActivityTestUtil from 'oae-activity/lib/test/util'; +import * as ActivityUtil from 'oae-activity/lib/util'; +import { AssociationsSession } from 'oae-activity/lib/model'; // Keep a safe reference to the get aggregate status function as we will // patch it in some of these tests @@ -151,11 +149,13 @@ describe('Activity', () => { // A single entity } + if (activity[entity]['oae:id']) { return [activity[entity]]; // A collection of entities } + return activity[entity]['oae:collection']; }; @@ -216,6 +216,7 @@ describe('Activity', () => { return callback(); }); }; + /** * Creates 2 tenants with a set of users, groups and discussions. The first * tenant will contain an extra public user that will be used to share discussions @@ -918,8 +919,8 @@ describe('Activity', () => { assert.ok(!err); /*! - * @return a valid activity seed that can be overlayed with invalid values for testing. - */ + * @return a valid activity seed that can be overlayed with invalid values for testing. + */ const _createActivitySeed = function(seedOverlay, actorOverlay, objectOverlay, targetOverlay) { if (!seedOverlay) { return null; @@ -2826,6 +2827,7 @@ describe('Activity', () => { // this set, simply pass up to the regular function return activityDaoGetAggregateStatusFn(allAggregateKeys, callback); } + // There is an aggregate key we've flagged as broken, // mock an error return callback({ code: 500, msg: 'Forced error for test' }); diff --git a/packages/oae-activity/tests/test-email.js b/packages/oae-activity/tests/test-email.js index 99c0960841..838d80e29b 100644 --- a/packages/oae-activity/tests/test-email.js +++ b/packages/oae-activity/tests/test-email.js @@ -15,26 +15,24 @@ /* eslint-disable no-unused-vars */ -const assert = require('assert'); -const util = require('util'); -const $ = require('cheerio'); -const _ = require('underscore'); - -const ConfigTestUtil = require('oae-config/lib/test/util'); -const ContentTestUtil = require('oae-content/lib/test/util'); -const EmailTestUtil = require('oae-email/lib/test/util'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); -const RestAPI = require('oae-rest'); -const Sanitization = require('oae-util/lib/sanitization'); -const TenantsTestUtil = require('oae-tenants/lib/test/util'); -const TestsUtil = require('oae-tests'); -const TZ = require('oae-util/lib/tz'); - -const ActivityAPI = require('oae-activity'); -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityEmail = require('oae-activity/lib/internal/email'); -const ActivitySystemConfig = require('oae-activity/lib/internal/config'); -const ActivityTestUtil = require('oae-activity/lib/test/util'); +import assert from 'assert'; +import util from 'util'; +import $ from 'cheerio'; +import _ from 'underscore'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as ContentTestUtil from 'oae-content/lib/test/util'; +import * as EmailTestUtil from 'oae-email/lib/test/util'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as Sanitization from 'oae-util/lib/sanitization'; +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; +import * as TestsUtil from 'oae-tests'; +import * as TZ from 'oae-util/lib/tz'; +import * as ActivityAPI from 'oae-activity'; +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as ActivityEmail from 'oae-activity/lib/internal/email'; +import * as ActivitySystemConfig from 'oae-activity/lib/internal/config'; +import * as ActivityTestUtil from 'oae-activity/lib/test/util'; describe('Activity Email', () => { // Rest contexts that can be used every time we need to make a request as an admin @@ -93,6 +91,7 @@ describe('Activity Email', () => { } else { dailyHour = now.getHours() + 5; } + dailyHour %= 24; let weeklyHour = null; @@ -808,6 +807,7 @@ describe('Activity Email', () => { if (userIds.length === 24) { return allTenantsCreated(); } + // Create a tenant const alias = TenantsTestUtil.generateTestTenantAlias(); const host = TenantsTestUtil.generateTestTenantHost(); diff --git a/packages/oae-activity/tests/test-notifications.js b/packages/oae-activity/tests/test-notifications.js index 5bba30f4c0..0364382656 100644 --- a/packages/oae-activity/tests/test-notifications.js +++ b/packages/oae-activity/tests/test-notifications.js @@ -14,19 +14,19 @@ */ /* eslint-disable no-unused-vars */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const { ContentConstants } = require('oae-content/lib/constants'); -const EmailTestsUtil = require('oae-email/lib/test/util'); -const OaeUtil = require('oae-util/lib/util'); -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); +import { ContentConstants } from 'oae-content/lib/constants'; +import * as EmailTestsUtil from 'oae-email/lib/test/util'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityModel = require('oae-activity/lib/model'); -const ActivityRouter = require('oae-activity/lib/internal/router'); -const ActivityTestsUtil = require('oae-activity/lib/test/util'); +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as ActivityModel from 'oae-activity/lib/model'; +import * as ActivityRouter from 'oae-activity/lib/internal/router'; +import * as ActivityTestsUtil from 'oae-activity/lib/test/util'; describe('Notifications', () => { // Rest context that can be used every time we need to make a request as an anonymous user @@ -39,11 +39,11 @@ describe('Notifications', () => { let globalAdminRestContext = null; /*! - * Create a default activity configuration object, overridden with the given `overlay` object. - * - * @param {Object} overlay Configuration properties with which to overide the default. - * @return {Object} An object that represents the default configuration for unit tests, overridden by the overlay. - */ + * Create a default activity configuration object, overridden with the given `overlay` object. + * + * @param {Object} overlay Configuration properties with which to overide the default. + * @return {Object} An object that represents the default configuration for unit tests, overridden by the overlay. + */ const createDefaultConfig = function(overlay) { return _.extend({ collectionPollingFrequency: -1 }, overlay); }; @@ -216,16 +216,16 @@ describe('Notifications', () => { assert.strictEqual(me.notificationsUnread, 0); /*! - * Share two things at once before aggregating. This verifies the case where 2 - * items are aggregated together in memory, not in the feed. - * - * Note that just because 2 aggregating items are aggregating in the same cycle - * doesn't mean they're aggregated together in-memory, that only happens if they - * are dropped in the same routed activity bucket. If the config value - * `numberOfProcessingBuckets` is `1`, then it will happen all the time. If it - * is larger than `1` and this functionality regresses, then this will be an - * intermittent test failure. - */ + * Share two things at once before aggregating. This verifies the case where 2 + * items are aggregated together in memory, not in the feed. + * + * Note that just because 2 aggregating items are aggregating in the same cycle + * doesn't mean they're aggregated together in-memory, that only happens if they + * are dropped in the same routed activity bucket. If the config value + * `numberOfProcessingBuckets` is `1`, then it will happen all the time. If it + * is larger than `1` and this functionality regresses, then this will be an + * intermittent test failure. + */ RestAPI.Content.createLink( mrvisser.restContext, @@ -503,8 +503,8 @@ describe('Notifications', () => { describe('Mail deduplication', () => { /*! - * Flush the mail queue so other tests don't impact the mail deduplication tests - */ + * Flush the mail queue so other tests don't impact the mail deduplication tests + */ beforeEach(EmailTestsUtil.clearEmailCollections); /** diff --git a/packages/oae-activity/tests/test-push.js b/packages/oae-activity/tests/test-push.js index e4cd488814..fbbb3c30b4 100644 --- a/packages/oae-activity/tests/test-push.js +++ b/packages/oae-activity/tests/test-push.js @@ -13,15 +13,14 @@ * permissions and limitations under the License. */ -/* eslint-disable no-unused-vars */ /* eslint-disable max-nested-callbacks */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; -const ActivityTestUtil = require('oae-activity/lib/test/util'); +import * as ActivityTestUtil from 'oae-activity/lib/test/util'; describe('Activity push', () => { // Rest context that can be used every time we need to make a request as a tenant admin @@ -791,11 +790,11 @@ describe('Activity push', () => { assert.ok(!err); /* - * Register a push client for mrvisser who is subscribed to: - * * `activity`-stream with the `activitystream` format - * * `activity`-stream with the `internal` format - * * `notification`-stream with the `internal` format - */ + * Register a push client for mrvisser who is subscribed to: + * * `activity`-stream with the `activitystream` format + * * `activity`-stream with the `internal` format + * * `notification`-stream with the `internal` format + */ const data = { authentication: { userId: mrvisserMeData.id, @@ -874,11 +873,11 @@ describe('Activity push', () => { assert.ok(!err); /* - * Register a push client for mrvisser who is subscribed to: - * * `activity`-stream with the `activitystream` format - * * `activity`-stream with the `internal` format - * * `notification`-stream with the `internal` format - */ + * Register a push client for mrvisser who is subscribed to: + * * `activity`-stream with the `activitystream` format + * * `activity`-stream with the `internal` format + * * `notification`-stream with the `internal` format + */ const data = { authentication: { userId: mrvisserMeData.id, diff --git a/packages/oae-authentication/config/strategies.js b/packages/oae-authentication/config/strategies.js index 0239a133f8..f88b435b0f 100644 --- a/packages/oae-authentication/config/strategies.js +++ b/packages/oae-authentication/config/strategies.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const Fields = require('oae-config/lib/fields'); +import { Bool, Text } from 'oae-config/lib/fields'; module.exports = { title: 'OAE Authentication Module', @@ -21,85 +21,46 @@ module.exports = { name: 'Local Authentication', description: 'Allow local authentication for tenant', elements: { - allowAccountCreation: new Fields.Bool( - 'Local Account Creation', - 'Allow users to create their own account', - true - ), - enabled: new Fields.Bool( - 'Local Authentication Enabled', - 'Allow local authentication for tenant', - true - ) + allowAccountCreation: new Bool('Local Account Creation', 'Allow users to create their own account', true), + enabled: new Bool('Local Authentication Enabled', 'Allow local authentication for tenant', true) } }, google: { name: 'Google Authentication', description: 'Allow Google authentication for tenant', elements: { - enabled: new Fields.Bool( - 'Google Authentication Enabled', - 'Allow Google authentication for tenant', - false - ), - key: new Fields.Text('Google client ID', 'Google client ID', process.env.GOOGLE_CLIENT_ID, { + enabled: new Bool('Google Authentication Enabled', 'Allow Google authentication for tenant', false), + key: new Text('Google client ID', 'Google client ID', process.env.GOOGLE_CLIENT_ID, { suppress: true }), - secret: new Fields.Text( - 'Google client secret', - 'Google client secret', - process.env.GOOGLE_CLIENT_SECRET, - { - suppress: true - } - ), - domains: new Fields.Text( - 'Google domain(s)', - 'A comma-separated list of allowed email domains (optional)', - '' - ) + secret: new Text('Google client secret', 'Google client secret', process.env.GOOGLE_CLIENT_SECRET, { + suppress: true + }), + domains: new Text('Google domain(s)', 'A comma-separated list of allowed email domains (optional)', '') } }, twitter: { name: 'Twitter Authentication', description: 'Allow Twitter authentication for tenant', elements: { - enabled: new Fields.Bool( - 'Twitter Authentication Enabled', - 'Allow Twitter authentication for tenant', - true - ), - key: new Fields.Text( - 'Twitter consumer key', - 'Twitter consumer key', - process.env.TWITTER_KEY, - { - suppress: true - } - ), - secret: new Fields.Text( - 'Twitter consumer secret', - 'Twitter consumer secret', - process.env.TWITTER_SECRET, - { - suppress: true - } - ) + enabled: new Bool('Twitter Authentication Enabled', 'Allow Twitter authentication for tenant', true), + key: new Text('Twitter consumer key', 'Twitter consumer key', process.env.TWITTER_KEY, { + suppress: true + }), + secret: new Text('Twitter consumer secret', 'Twitter consumer secret', process.env.TWITTER_SECRET, { + suppress: true + }) } }, facebook: { name: 'Facebook Authentication', description: 'Allow Facebook authentication for tenant', elements: { - enabled: new Fields.Bool( - 'Facebook Authentication Enabled', - 'Allow Facebook authentication for tenant', - false - ), - appid: new Fields.Text('Facebook App ID', 'Facebook App ID', process.env.FACEBOOK_APP_ID, { + enabled: new Bool('Facebook Authentication Enabled', 'Allow Facebook authentication for tenant', false), + appid: new Text('Facebook App ID', 'Facebook App ID', process.env.FACEBOOK_APP_ID, { suppress: true }), - secret: new Fields.Text('Secret', 'Secret', process.env.FACEBOOK_APP_SECRET, { + secret: new Text('Secret', 'Secret', process.env.FACEBOOK_APP_SECRET, { suppress: true }) } @@ -108,38 +69,30 @@ module.exports = { name: 'Shibboleth Authentication', description: 'Allow Shibboleth authentication for tenant', elements: { - enabled: new Fields.Bool( - 'Shibboleth Authentication Enabled', - 'Allow Shibboleth authentication for tenant', - false - ), - name: new Fields.Text( - 'Name', - 'A name that users will recognize as their identity provider', - '' - ), - idpEntityID: new Fields.Text('Identity Provider entity ID', 'The entity ID of the IdP', '', { + enabled: new Bool('Shibboleth Authentication Enabled', 'Allow Shibboleth authentication for tenant', false), + name: new Text('Name', 'A name that users will recognize as their identity provider', ''), + idpEntityID: new Text('Identity Provider entity ID', 'The entity ID of the IdP', '', { suppress: true }), - externalIdAttributes: new Fields.Text( + externalIdAttributes: new Text( 'External ID Attribute', 'The attribute that uniquely identifies the user. This should be a prioritised space seperated list', 'persistent-id targeted-id eppn', { suppress: true } ), - mapDisplayName: new Fields.Text( + mapDisplayName: new Text( 'Display name', 'The attibute(s) that should be used to construct the displayname. This should be a prioritised space seperated list. e.g., `displayname cn`', 'displayname cn', { suppress: true } ), - mapEmail: new Fields.Text( + mapEmail: new Text( 'Email', 'The attibute(s) that should be used to construct the email. This should be a prioritised space seperated list. e.g., `mail email eppn`', 'mail email eppn', { suppress: true } ), - mapLocale: new Fields.Text( + mapLocale: new Text( 'Locale', 'The attibute(s) that should be used to construct the locale. This should be a prioritised space seperated list. e.g., `locality locale`', 'locality locale', @@ -151,124 +104,106 @@ module.exports = { name: 'CAS Authentication', description: 'Allow CAS authentication for tenant', elements: { - enabled: new Fields.Bool( - 'CAS Authentication Enabled', - 'Allow CAS authentication for tenant', - false - ), - name: new Fields.Text( - 'Name', - 'A name that users will recognize as their identity provider', - '' - ), - url: new Fields.Text( + enabled: new Bool('CAS Authentication Enabled', 'Allow CAS authentication for tenant', false), + name: new Text('Name', 'A name that users will recognize as their identity provider', ''), + url: new Text( 'Host', 'The URL at which the CAS server can be reached. This should include http(s)://, any non-standard port and any base path with no trailing slash', '', { suppress: true } ), - loginPath: new Fields.Text( + loginPath: new Text( 'Login Path', 'The path to which the user should be redirected to start the authentication flow', '/login', { suppress: true } ), - useSaml: new Fields.Bool( + useSaml: new Bool( 'Use SAML', 'Use SAML to get CAS attributes. When using this, you probably need to set the Validate Path to "/samlValidate"', false, { suppress: true } ), - validatePath: new Fields.Text( + validatePath: new Text( 'CAS Validate Path', 'The CAS validation path such as /serviceValdiate', '/serviceValidate', { suppress: true } ), - logoutUrl: new Fields.Text( + logoutUrl: new Text( 'Logout URL', 'The URL to which the user should be redirected when logging out of OAE. This should be a full url including a valid protocol (e.g., https://my.cas.server/cas/logout)', '', { suppress: true } ), - mapDisplayName: new Fields.Text( + mapDisplayName: new Text( 'Display name', 'The attibute(s) that should be used to construct the displayname. e.g., {first_name} {last_name}', '', { suppress: true } ), - mapEmail: new Fields.Text( - 'Email', - 'The attibute(s) that should be used to construct the email. e.g., {mail}', - '', - { suppress: true } - ), - mapLocale: new Fields.Text( - 'Locale', - 'The attibute(s) that should be used to construct the locale. e.g., {locale}', - '', - { suppress: true } - ) + mapEmail: new Text('Email', 'The attibute(s) that should be used to construct the email. e.g., {mail}', '', { + suppress: true + }), + mapLocale: new Text('Locale', 'The attibute(s) that should be used to construct the locale. e.g., {locale}', '', { + suppress: true + }) } }, ldap: { name: 'LDAP Authentication', description: 'Allow LDAP authentication for tenant', elements: { - enabled: new Fields.Bool( - 'LDAP Authentication Enabled', - 'Allow LDAP authentication for tenant', - false - ), - url: new Fields.Text( + enabled: new Bool('LDAP Authentication Enabled', 'Allow LDAP authentication for tenant', false), + url: new Text( 'Host', 'The URL at which the LDAP server can be reached. This should include both the protocol and the port. E.g. `ldaps://lookup.example.com:636` (required)', '', { suppress: true } ), - adminDn: new Fields.Text( + adminDn: new Text( 'Admin Distinguished Name', 'The DN that identifies an admin user that can search for user information. E.g. uid=admin,ou=users,dc=example,dc=com (required)', '', { suppress: true } ), - adminPassword: new Fields.Text( + adminPassword: new Text( 'Admin password', 'The password for the admin DN that can be used to bind to LDAP. (required)', '', { suppress: true } ), - searchBase: new Fields.Text( + searchBase: new Text( 'Base', 'The base DN under which to search for users. E.g. ou=users,dc=example,dc=com (required)', '', { suppress: true } ), - searchFilter: new Fields.Text( + searchFilter: new Text( 'Filter', 'The LDAP search filter with which to find a user by username, e.g. (uid={{username}}). Use the literal `{{username}}` to have the given username be interpolated in for the LDAP search. (required)', '', { suppress: true } ), - mapExternalId: new Fields.Text( + mapExternalId: new Text( 'LDAP External ID field', 'The name of the LDAP field that contains an identifier that uniquely identifies the user in LDAP (required)', 'uid', { suppress: true } ), - mapDisplayName: new Fields.Text( + mapDisplayName: new Text( 'LDAP DisplayName field', "The name of the LDAP field that contains the user's displayName (required)", 'cn', { suppress: true } ), - mapEmail: new Fields.Text( + mapEmail: new Text( 'LDAP Email field', "The name of the LDAP field that contains the user's email address (optional)", '', { suppress: true } ), - mapLocale: new Fields.Text( + mapLocale: new Text( 'LDAP Locale field', "The name of the LDAP field that contains the user's locale (optional)", '', diff --git a/packages/oae-authentication/emailTemplates/reset.shared.js b/packages/oae-authentication/emailTemplates/reset.shared.js index 1e92c96d60..ffa82ad528 100644 --- a/packages/oae-authentication/emailTemplates/reset.shared.js +++ b/packages/oae-authentication/emailTemplates/reset.shared.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const $ = require('cheerio'); +import $ from 'cheerio'; /** * Ensure that a link is an absolute URL. If a relative link is @@ -31,11 +31,13 @@ const ensureAbsoluteLink = function(link, baseUrl) { // If the link already has `http` in it (e.g., twitter profile pics) we return as-is } + if (link.indexOf('http') === 0) { return link; // Otherwise we prefix it with the base url } + return baseUrl + link; }; @@ -57,7 +59,4 @@ const ensureAbsoluteLinks = function(str, baseUrl) { return html.html(); }; -module.exports = { - ensureAbsoluteLink, - ensureAbsoluteLinks -}; +export { ensureAbsoluteLink, ensureAbsoluteLinks }; diff --git a/packages/oae-authentication/lib/api.js b/packages/oae-authentication/lib/api.js index 9cb8fe4b4c..1bf6faf6ce 100644 --- a/packages/oae-authentication/lib/api.js +++ b/packages/oae-authentication/lib/api.js @@ -13,31 +13,33 @@ * permissions and limitations under the License. */ -const crypto = require('crypto'); -const util = require('util'); -const _ = require('underscore'); -const passport = require('passport'); - -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzInvitationsDAO = require('oae-authz/lib/invitations/dao'); -const Cassandra = require('oae-util/lib/cassandra'); -const ConfigAPI = require('oae-config'); -const EmitterAPI = require('oae-emitter'); -const Locking = require('oae-util/lib/locking'); -const EmailAPI = require('oae-email'); -const OaeEmitter = require('oae-util/lib/emitter'); -const OaeUtil = require('oae-util/lib/util'); -const PrincipalsAPI = require('oae-principals'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const TenantsAPI = require('oae-tenants'); -const TenantsUtil = require('oae-tenants/lib/util'); -const log = require('oae-logger').logger('oae-authentication'); -const { Validator } = require('oae-authz/lib/validator'); - -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationUtil = require('oae-authentication/lib/util'); - -const { LoginId } = require('oae-authentication/lib/model'); +import crypto from 'crypto'; +import util from 'util'; +import _ from 'underscore'; +import passport from 'passport'; + +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as AuthzInvitationsDAO from 'oae-authz/lib/invitations/dao'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as ConfigAPI from 'oae-config'; +import * as EmitterAPI from 'oae-emitter'; +import * as Locking from 'oae-util/lib/locking'; +import * as EmailAPI from 'oae-email'; +import OaeEmitter from 'oae-util/lib/emitter'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as PrincipalsAPI from 'oae-principals'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as TenantsAPI from 'oae-tenants'; +import * as TenantsUtil from 'oae-tenants/lib/util'; +import { logger } from 'oae-logger'; +import { Validator } from 'oae-authz/lib/validator'; +import { getTenantSkinVariables } from 'oae-ui'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; + +import { LoginId } from 'oae-authentication/lib/model'; + +const log = logger('oae-authentication'); let globalTenantAlias = null; @@ -81,10 +83,7 @@ const _configUpdate = function(tenantAlias) { } else { const tenant = TenantsAPI.getTenant(tenantAlias); if (!tenant) { - return log().error( - { tenantAlias }, - 'Error fetching tenant to update authentication configuration' - ); + return log().error({ tenantAlias }, 'Error fetching tenant to update authentication configuration'); } refreshStrategies(tenant); @@ -149,6 +148,7 @@ const localUsernameExists = function(ctx, tenantAlias, username, callback) { if (err && err.code !== 404) { return callback(err); } + if (!userId) { return callback(null, false); } @@ -184,8 +184,7 @@ const getOrCreateGlobalAdminUser = function(ctx, username, password, displayName validator .check(null, { code: 401, - msg: - 'You must be authenticated to the global admin tenant to create a global administrator user' + msg: 'You must be authenticated to the global admin tenant to create a global administrator user' }) .isLoggedInUser(ctx, globalTenantAlias); validator @@ -223,6 +222,7 @@ const getOrCreateGlobalAdminUser = function(ctx, username, password, displayName if (err) { return callback(err); } + if (!created) { // The user already existed, just return the existing user return callback(null, user, loginId, false); @@ -233,6 +233,7 @@ const getOrCreateGlobalAdminUser = function(ctx, username, password, displayName if (err) { return callback(err); } + if (created) { log().info({ user, username }, 'Global Admin account created'); } @@ -270,15 +271,7 @@ const getOrCreateGlobalAdminUser = function(ctx, username, password, displayName * @param {String} callback.loginId The *flattened* loginId for this user * @param {Boolean} callback.created `true` if the user was created, `false` otherwise */ -const getOrCreateUser = function( - ctx, - authProvider, - externalId, - providerProperties, - displayName, - opts, - callback -) { +const getOrCreateUser = function(ctx, authProvider, externalId, providerProperties, displayName, opts, callback) { const validator = new Validator(); validator.check(displayName, { code: 400, msg: 'You must provide a display name' }).notEmpty(); validator @@ -321,12 +314,14 @@ const _getOrCreateUser = function(ctx, loginId, displayName, opts, callback) { if (err && err.code !== 404) { return callback(err); } + if (userId) { // The user existed, simply fetch their profile PrincipalsDAO.getPrincipal(userId, (err, user) => { if (err) { return callback(err); } + if (user.deleted) { return callback({ code: 401, msg: 'Provided login id belongs to a deleted user' }); } @@ -363,6 +358,7 @@ const _getOrCreateUser = function(ctx, loginId, displayName, opts, callback) { if (err) { return callback(err); } + if (email && !opts.email) { // If no email is provided by the auth provider, set the email associated to the // token as the verified email @@ -379,8 +375,7 @@ const _getOrCreateUser = function(ctx, loginId, displayName, opts, callback) { // If an email address was provided by a non authoritative source (Facebook, Twitter, // Google, Local authentication) by a user that is not an administrator we should // check whether the email address matches the tenant's configured email domain - let shouldCheckEmail = - !_.isEmpty(ctx.tenant().emailDomains) && !isAdmin && !opts.authoritative; + let shouldCheckEmail = !_.isEmpty(ctx.tenant().emailDomains) && !isAdmin && !opts.authoritative; // However, if a user followed a link from an invitation email we do not check whether // the email belongs to the configured tenant's email domain. This is to allow for the @@ -390,25 +385,19 @@ const _getOrCreateUser = function(ctx, loginId, displayName, opts, callback) { shouldCheckEmail = false; } - OaeUtil.invokeIfNecessary( - shouldCheckEmail, - _validateEmailBelongsToTenant, - ctx, - opts.email, - err => { + OaeUtil.invokeIfNecessary(shouldCheckEmail, _validateEmailBelongsToTenant, ctx, opts.email, err => { + if (err) { + return callback(err); + } + + createUser(ctx, loginId, displayName, opts, (err, user) => { if (err) { return callback(err); } - createUser(ctx, loginId, displayName, opts, (err, user) => { - if (err) { - return callback(err); - } - - return callback(null, user, _flattenLoginId(loginId), true); - }); - } - ); + return callback(null, user, _flattenLoginId(loginId), true); + }); + }); } ); } @@ -483,12 +472,14 @@ const createTenantAdminUser = function(ctx, loginId, displayName, opts, callback msg: 'A non-existing tenant was specified as the target for this user' }); } + if (targetTenant.isGlobalAdminServer) { return callback({ code: 400, msg: 'A tenant administrator cannot be created on the global admin tenant' }); } + if (!ctx.user() || !ctx.user().isAdmin(targetTenant.alias)) { return callback({ code: 401, msg: 'Only administrators can create new tenant administrators' }); } @@ -561,6 +552,7 @@ const createUser = function(ctx, loginId, displayName, opts, callback) { msg: 'Only global administrators may create a user on the global admin tenant' }); } + if (ctx.tenant().alias !== targetTenant.alias && !isGlobalAdmin) { // Only global admins can create users on a tenant other than the current return callback({ @@ -598,6 +590,7 @@ const _createUser = function(ctx, loginId, displayName, opts, callback) { if (err) { return callback(err); } + if (!lockToken) { return callback({ code: 400, @@ -610,6 +603,7 @@ const _createUser = function(ctx, loginId, displayName, opts, callback) { if (err && err.code !== 404) { return callback(err); } + if (userId) { return callback({ code: 400, @@ -695,6 +689,7 @@ const associateLoginId = function(ctx, loginId, userId, callback) { if (err && err.code !== 404) { return callback(err); } + if (existingUserIdMapping && !isAdmin) { // Only admin can re-associate a login id to another user return callback({ code: 401, msg: 'Login ID is already associated to a user' }); @@ -705,6 +700,7 @@ const associateLoginId = function(ctx, loginId, userId, callback) { if (err) { return callback(err); } + if (loginIds[loginId.provider]) { return callback({ code: 400, @@ -717,6 +713,7 @@ const associateLoginId = function(ctx, loginId, userId, callback) { if (err) { return callback(err); } + if (user.deleted) { return callback({ code: 404, msg: util.format("Couldn't find principal: ", userId) }); } @@ -772,6 +769,7 @@ const changePassword = function(ctx, userId, oldPassword, newPassword, callback) if (err) { return callback(err); } + if (user.deleted) { return callback({ code: 404, msg: util.format("Couldn't find principal: ", userId) }); } @@ -792,13 +790,10 @@ const changePassword = function(ctx, userId, oldPassword, newPassword, callback) const isAdmin = ctx.user().isAdmin(localLoginId.tenantAlias); const isTargetUser = ctx.user().id === userId; if (!isAdmin && !isTargetUser) { - log().info( - 'Failed attempt to change password for user %s by user %s', - userId, - ctx.user().id - ); + log().info('Failed attempt to change password for user %s by user %s', userId, ctx.user().id); return callback({ code: 401, msg: "You're not authorized to change this user's password" }); } + if (isAdmin) { // If the user is admin we don't care about the old password log().info('User %s is changing the password for user %s', ctx.user().id, userId); @@ -856,6 +851,7 @@ const checkPassword = function(tenantAlias, username, password, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { // No user found with that login id return callback({ code: 401, msg: 'No password found for this principal' }); @@ -864,9 +860,7 @@ const checkPassword = function(tenantAlias, username, password, callback) { // Check if the user provided password matches the stored password const result = Cassandra.rowToHash(rows[0]); const passwordMatches = - result.userId && - result.password && - AuthenticationUtil.hashAndComparePassword(password, result.password); + result.userId && result.password && AuthenticationUtil.hashAndComparePassword(password, result.password); if (passwordMatches) { callback(null, result.userId); } else { @@ -926,6 +920,7 @@ const getResetPasswordSecret = function(ctx, username, callback) { if (err) { return callback(err); } + if (!user.email) { log().warn({ userId }, 'Used asked for password reset but has no email address'); return callback({ @@ -953,7 +948,7 @@ const getResetPasswordSecret = function(ctx, username, callback) { user, username, baseUrl: TenantsUtil.getBaseUrl(ctx.tenant()), - skinVariables: require('oae-ui').getTenantSkinVariables(ctx.tenant().alias), + skinVariables: getTenantSkinVariables(ctx.tenant().alias), secret }; @@ -981,9 +976,7 @@ const resetPassword = function(ctx, username, secret, newPassword, callback) { validator.check(username, { code: 400, msg: 'A username must be provided' }).notEmpty(); validator.check(secret, { code: 400, msg: 'A secret must be provided' }).notEmpty(); validator.check(newPassword, { code: 400, msg: 'A new password must be provided' }).notEmpty(); - validator - .check(newPassword, { code: 400, msg: 'Must specify a password at least 6 characters long' }) - .len(6); + validator.check(newPassword, { code: 400, msg: 'Must specify a password at least 6 characters long' }).len(6); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -1002,6 +995,7 @@ const resetPassword = function(ctx, username, secret, newPassword, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { // No user found with that login id return callback({ code: 401, msg: 'No user found for this login ID' }); @@ -1017,6 +1011,7 @@ const resetPassword = function(ctx, username, secret, newPassword, callback) { // If the secret column was found. } + return _changePassword(loginId, newPassword, callback); } ); @@ -1065,22 +1060,17 @@ const _associateLoginId = function(loginId, userId, callback) { delete loginId.properties.loginId; loginId.properties.userId = userId; - const query = Cassandra.constructUpsertCQL( - 'AuthenticationLoginId', - 'loginId', - flattenedLoginId, - loginId.properties - ); + const query = Cassandra.constructUpsertCQL('AuthenticationLoginId', 'loginId', flattenedLoginId, loginId.properties); if (query) { const queries = []; queries.push(query); queries.push({ - query: - 'INSERT INTO "AuthenticationUserLoginId" ("userId", "loginId", "value") VALUES (?, ?, ?)', + query: 'INSERT INTO "AuthenticationUserLoginId" ("userId", "loginId", "value") VALUES (?, ?, ?)', parameters: [userId, flattenedLoginId, '1'] }); return Cassandra.runBatchQuery(queries, callback); } + log().error( { loginId, @@ -1116,6 +1106,7 @@ const getUserLoginIds = function(ctx, userId, callback) { if (err) { return callback(err); } + if (!ctx.user().isAdmin(user.tenant.alias)) { // Only global administrators and administrators of the tenant the user belongs to can request the login ids return callback({ @@ -1154,26 +1145,22 @@ const _getUserLoginIds = function(userId, callback) { return callback(null, {}); } - Cassandra.runQuery( - 'SELECT "loginId" FROM "AuthenticationUserLoginId" WHERE "userId" = ?', - [userId], - (err, rows) => { - if (err) { - return callback(err); - } + Cassandra.runQuery('SELECT "loginId" FROM "AuthenticationUserLoginId" WHERE "userId" = ?', [userId], (err, rows) => { + if (err) { + return callback(err); + } - const loginIds = {}; - _.each(rows, row => { - row = Cassandra.rowToHash(row); - if (row.loginId) { - const loginId = _expandLoginId(row.loginId); - loginIds[loginId.provider] = loginId; - } - }); + const loginIds = {}; + _.each(rows, row => { + row = Cassandra.rowToHash(row); + if (row.loginId) { + const loginId = _expandLoginId(row.loginId); + loginIds[loginId.provider] = loginId; + } + }); - return callback(null, loginIds); - } - ); + return callback(null, loginIds); + }); }; /** @@ -1193,6 +1180,7 @@ const _getUserIdFromLoginId = function(loginId, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback({ code: 404, msg: 'No user could be found with the provided login id' }); } @@ -1216,6 +1204,7 @@ const _flattenLoginId = function(loginId) { if (!loginId || !loginId.tenantAlias || !loginId.provider || !loginId.externalId) { return null; } + return loginId.tenantAlias + ':' + loginId.provider + ':' + loginId.externalId; }; @@ -1252,18 +1241,14 @@ const _validateLoginIdForLookup = function(validator, loginId) { validator.check(null, { code: 400, msg: 'Must specify a login id' }).isObject(loginId); if (validator.getErrorCount() === numErrors) { // Only validate these if loginId is a valid object - validator - .check(loginId.tenantAlias, { code: 400, msg: 'Must specify a tenant id on the login id' }) - .notEmpty(); + validator.check(loginId.tenantAlias, { code: 400, msg: 'Must specify a tenant id on the login id' }).notEmpty(); validator .check(loginId.provider, { code: 400, msg: 'Must specify an authentication provider on the login id' }) .notEmpty(); - validator - .check(loginId.externalId, { code: 400, msg: 'Must specify an external id on the login id' }) - .notEmpty(); + validator.check(loginId.externalId, { code: 400, msg: 'Must specify an external id on the login id' }).notEmpty(); } }; @@ -1413,7 +1398,7 @@ const logout = function(req, res) { return strategy.logout(req, res); }; -module.exports = { +export { emitter, init, localUsernameExists, diff --git a/packages/oae-authentication/lib/constants.js b/packages/oae-authentication/lib/constants.js index bcce50a4e5..a30e949bb1 100644 --- a/packages/oae-authentication/lib/constants.js +++ b/packages/oae-authentication/lib/constants.js @@ -39,4 +39,4 @@ AuthenticationConstants.events = { USER_LOGGED_OUT: 'userLoggedOut' }; -module.exports = { AuthenticationConstants }; +export { AuthenticationConstants }; diff --git a/packages/oae-authentication/lib/init.js b/packages/oae-authentication/lib/init.js index dd6252198b..ada21c512e 100644 --- a/packages/oae-authentication/lib/init.js +++ b/packages/oae-authentication/lib/init.js @@ -13,18 +13,26 @@ * permissions and limitations under the License. */ -const crypto = require('crypto'); -const passport = require('passport'); - -const { Context } = require('oae-context'); -const OAE = require('oae-util/lib/oae'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); - -const AuthenticationAPI = require('./api'); -const { AuthenticationConstants } = require('./constants'); -const AuthenticationUtil = require('./util'); - -module.exports = function(config, callback) { +import crypto from 'crypto'; +import passport from 'passport'; +import { Context } from 'oae-context'; +import * as OAE from 'oae-util/lib/oae'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import initCas from './strategies/cas/init'; +import initFacebook from './strategies/facebook/init'; +import initGoogle from './strategies/google/init'; +import initLDAP from './strategies/ldap/init'; +import initLocal from './strategies/local/init'; +import initOAuth from './strategies/oauth/init'; +import initShibb from './strategies/shibboleth/init'; +import initSigned from './strategies/signed/init'; +import initTwitter from './strategies/twitter/init'; + +import * as AuthenticationAPI from './api'; +import { AuthenticationConstants } from './constants'; +import * as AuthenticationUtil from './util'; + +export function init(config, callback) { // Attach the Authentication middleware AuthenticationUtil.setupAuthMiddleware(config, OAE.globalAdminServer); AuthenticationUtil.setupAuthMiddleware(config, OAE.tenantServer); @@ -34,15 +42,15 @@ module.exports = function(config, callback) { AuthenticationAPI.init(config.servers.globalAdminAlias); - require('./strategies/cas/init')(config); - require('./strategies/facebook/init')(config); - require('./strategies/google/init')(config); - require('./strategies/ldap/init')(config); - require('./strategies/local/init')(config); - require('./strategies/oauth/init')(config); - require('./strategies/shibboleth/init')(config); - require('./strategies/signed/init')(config); - require('./strategies/twitter/init')(config); + initCas(config); + initFacebook(config); + initGoogle(config); + initLDAP(config); + initLocal(config); + initOAuth(config); + initShibb(config); + initSigned(config); + initTwitter(config); // Add the OAE middleware to the ExpressJS server // We do this *AFTER* all the authentication strategies have been initialized @@ -51,7 +59,7 @@ module.exports = function(config, callback) { OAE.globalAdminServer.use(contextMiddleware); return callback(); -}; +} /** * Express.js middleware that will stick an OAE `Context` object on each request at `req.ctx`. This @@ -98,6 +106,7 @@ const setupPassportSerializers = function(cookieSecret) { if (oaeAuthInfo.imposter) { toSerialize.imposterId = oaeAuthInfo.imposter.id; } + if (oaeAuthInfo.strategyId) { toSerialize.strategyId = oaeAuthInfo.strategyId; } @@ -148,14 +157,17 @@ const setupPassportSerializers = function(cookieSecret) { // If the user does not exist, the session is toast return callback(null, false); } + if (err) { // If an unexpected error occurred, return an error return callback(err); } + if (user.deleted) { // The user has been deleted, the session is toast return callback(null, false); } + if (!sessionData.imposterId) { // There is no impostering happening here, so we just // treat this like a normal session @@ -168,10 +180,12 @@ const setupPassportSerializers = function(cookieSecret) { // If the user does not exist, the session is toast return callback(null, false); } + if (err) { // If an unexpected error occurred, return an error return callback(err); } + if (imposterUser.deleted) { // Burn any sessions being impostered by a deleted user return callback(null, false); diff --git a/packages/oae-authentication/lib/migration.js b/packages/oae-authentication/lib/migration.js index 42e251ff6a..b4a4274297 100644 --- a/packages/oae-authentication/lib/migration.js +++ b/packages/oae-authentication/lib/migration.js @@ -1,4 +1,4 @@ -const Cassandra = require('oae-util/lib/cassandra'); +import { createColumnFamilies } from 'oae-util/lib/cassandra'; /** * Ensure that the all of the authentication-related schemas are created. If they already exist, this method will not do anything. @@ -8,7 +8,7 @@ const Cassandra = require('oae-util/lib/cassandra'); * @api private */ const ensureSchema = function(callback) { - Cassandra.createColumnFamilies( + createColumnFamilies( { AuthenticationLoginId: 'CREATE TABLE "AuthenticationLoginId" ("loginId" text PRIMARY KEY, "userId" text, "password" text, "secret" text)', @@ -28,4 +28,4 @@ const ensureSchema = function(callback) { ); }; -module.exports = { ensureSchema }; +export { ensureSchema }; diff --git a/packages/oae-authentication/lib/model.js b/packages/oae-authentication/lib/model.js index 14d44d6c9d..85a7463e5f 100644 --- a/packages/oae-authentication/lib/model.js +++ b/packages/oae-authentication/lib/model.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const { AuthenticationConstants } = require('./constants'); +import { AuthenticationConstants } from './constants'; /** * An object that represents a means to log in to the system. @@ -40,6 +40,4 @@ const LoginId = function(tenantAlias, provider, externalId, properties) { return that; }; -module.exports = { - LoginId -}; +export { LoginId }; diff --git a/packages/oae-authentication/lib/rest.js b/packages/oae-authentication/lib/rest.js index f36dae60c3..261d345382 100644 --- a/packages/oae-authentication/lib/rest.js +++ b/packages/oae-authentication/lib/rest.js @@ -12,32 +12,32 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -const OAE = require('oae-util/lib/oae'); +import * as OAE from 'oae-util/lib/oae'; -const AuthenticationAPI = require('oae-authentication'); +import * as AuthenticationAPI from 'oae-authentication'; /// /////////////////////////// // AUTHENTICATION PROVIDERS // /// /////////////////////////// // eslint-disable-next-line import/no-unassigned-import -require('./strategies/cas/rest'); +import cas from './strategies/cas/rest'; // eslint-disable-next-line import/no-unassigned-import -require('./strategies/facebook/rest'); +import facebook from './strategies/facebook/rest'; // eslint-disable-next-line import/no-unassigned-import -require('./strategies/google/rest'); +import google from './strategies/google/rest'; // eslint-disable-next-line import/no-unassigned-import -require('./strategies/ldap/rest'); +import ldap from './strategies/ldap/rest'; // eslint-disable-next-line import/no-unassigned-import -require('./strategies/local/rest'); +import local from './strategies/local/rest'; // eslint-disable-next-line import/no-unassigned-import -require('./strategies/oauth/rest'); +import oauth from './strategies/oauth/rest'; // eslint-disable-next-line import/no-unassigned-import -require('./strategies/shibboleth/rest'); +import shibb from './strategies/shibboleth/rest'; // eslint-disable-next-line import/no-unassigned-import -require('./strategies/signed/rest'); +import signed from './strategies/signed/rest'; // eslint-disable-next-line import/no-unassigned-import -require('./strategies/twitter/rest'); +import twitter from './strategies/twitter/rest'; /** * @REST postAuthLogout @@ -85,19 +85,13 @@ const _getResetPasswordSecret = function(req, res) { OAE.tenantRouter.on('get', '/api/auth/local/reset/secret/:username', _getResetPasswordSecret); const _resetPassword = function(req, res) { - AuthenticationAPI.resetPassword( - req.ctx, - req.params.username, - req.body.secret, - req.body.newPassword, - err => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(200).end(); + AuthenticationAPI.resetPassword(req.ctx, req.params.username, req.body.secret, req.body.newPassword, err => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + return res.status(200).end(); + }); }; /** diff --git a/packages/oae-authentication/lib/strategies/cas/init.js b/packages/oae-authentication/lib/strategies/cas/init.js index 5023f33375..be24128e74 100644 --- a/packages/oae-authentication/lib/strategies/cas/init.js +++ b/packages/oae-authentication/lib/strategies/cas/init.js @@ -13,30 +13,30 @@ * permissions and limitations under the License. */ -const ConfigAPI = require('oae-config'); -const log = require('oae-logger').logger('oae-authentication'); -const TenantsUtil = require('oae-tenants/lib/util'); +import * as ConfigAPI from 'oae-config'; +import { logger } from 'oae-logger'; +import * as TenantsUtil from 'oae-tenants/lib/util'; -const AuthenticationAPI = require('oae-authentication'); +import * as AuthenticationAPI from 'oae-authentication'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; -const AuthenticationConfig = ConfigAPI.config('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationUtil = require('oae-authentication/lib/util'); +import passport from 'passport-cas'; -const CasStrategy = require('passport-cas').Strategy; +const CasStrategy = passport.Strategy; -module.exports = function() { +const log = logger('oae-authentication'); + +const AuthenticationConfig = ConfigAPI.setUpConfig('oae-authentication'); + +export default function() { const strategy = {}; /** * @see oae-authentication/lib/strategy#shouldBeEnabled */ strategy.shouldBeEnabled = function(tenantAlias) { - return AuthenticationConfig.getValue( - tenantAlias, - AuthenticationConstants.providers.CAS, - 'enabled' - ); + return AuthenticationConfig.getValue(tenantAlias, AuthenticationConstants.providers.CAS, 'enabled'); }; /** @@ -44,16 +44,8 @@ module.exports = function() { */ strategy.getPassportStrategy = function(tenant) { // We fetch the config values *in* the getPassportStrategy so it can be re-configured at run-time. - const casHost = AuthenticationConfig.getValue( - tenant.alias, - AuthenticationConstants.providers.CAS, - 'url' - ); - const loginPath = AuthenticationConfig.getValue( - tenant.alias, - AuthenticationConstants.providers.CAS, - 'loginPath' - ); + const casHost = AuthenticationConfig.getValue(tenant.alias, AuthenticationConstants.providers.CAS, 'url'); + const loginPath = AuthenticationConfig.getValue(tenant.alias, AuthenticationConstants.providers.CAS, 'loginPath'); const validatePath = AuthenticationConfig.getValue( tenant.alias, AuthenticationConstants.providers.CAS, @@ -74,11 +66,7 @@ module.exports = function() { AuthenticationConstants.providers.CAS, 'mapLocale' ).toLowerCase(); - const useSaml = AuthenticationConfig.getValue( - tenant.alias, - AuthenticationConstants.providers.CAS, - 'useSaml' - ); + const useSaml = AuthenticationConfig.getValue(tenant.alias, AuthenticationConstants.providers.CAS, 'useSaml'); const serverBase = TenantsUtil.getBaseUrl(tenant); @@ -113,10 +101,7 @@ module.exports = function() { // If the CAS server returned attributes we try to map them to OAE profile parameters if (casResponse.attributes) { // Try to use a mapped displayname rather than the default CAS id - const mappedDisplayName = AuthenticationUtil.renderTemplate( - mapDisplayName, - casResponse.attributes - ); + const mappedDisplayName = AuthenticationUtil.renderTemplate(mapDisplayName, casResponse.attributes); if (mappedDisplayName) { displayName = mappedDisplayName; } @@ -148,11 +133,7 @@ module.exports = function() { */ strategy.logout = function(req, res) { const tenant = req.ctx.tenant(); - const logoutUrl = AuthenticationConfig.getValue( - tenant.alias, - AuthenticationConstants.providers.CAS, - 'logoutUrl' - ); + const logoutUrl = AuthenticationConfig.getValue(tenant.alias, AuthenticationConstants.providers.CAS, 'logoutUrl'); // If no logout URL is specified, we simply redirect to the index page if (!logoutUrl) { @@ -165,4 +146,4 @@ module.exports = function() { // Register our strategy AuthenticationAPI.registerStrategy(AuthenticationConstants.providers.CAS, strategy); -}; +} diff --git a/packages/oae-authentication/lib/strategies/cas/rest.js b/packages/oae-authentication/lib/strategies/cas/rest.js index 69ceec0ada..dbd3483608 100644 --- a/packages/oae-authentication/lib/strategies/cas/rest.js +++ b/packages/oae-authentication/lib/strategies/cas/rest.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -const OAE = require('oae-util/lib/oae'); +import * as OAE from 'oae-util/lib/oae'; -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationUtil = require('oae-authentication/lib/util'); +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; /** * @REST postAuthCas @@ -32,10 +32,7 @@ const AuthenticationUtil = require('oae-authentication/lib/util'); */ OAE.tenantRouter.on('post', '/api/auth/cas', (req, res, next) => { // Get the ID under which we registered this strategy for this tenant - const strategyId = AuthenticationUtil.getStrategyId( - req.tenant, - AuthenticationConstants.providers.CAS - ); + const strategyId = AuthenticationUtil.getStrategyId(req.tenant, AuthenticationConstants.providers.CAS); // Perform the initial authentication step AuthenticationUtil.handleExternalSetup(strategyId, null, req, res, next); @@ -54,11 +51,10 @@ OAE.tenantRouter.on('post', '/api/auth/cas', (req, res, next) => { */ OAE.tenantRouter.on('get', '/api/auth/cas/callback', (req, res, next) => { // Get the ID under which we registered this strategy for this tenant - const strategyId = AuthenticationUtil.getStrategyId( - req.tenant, - AuthenticationConstants.providers.CAS - ); + const strategyId = AuthenticationUtil.getStrategyId(req.tenant, AuthenticationConstants.providers.CAS); // Log the user in AuthenticationUtil.handleExternalCallback(strategyId, req, res, next); }); + +export default OAE; diff --git a/packages/oae-authentication/lib/strategies/facebook/init.js b/packages/oae-authentication/lib/strategies/facebook/init.js index 115f3de8c1..1b2dbb1061 100644 --- a/packages/oae-authentication/lib/strategies/facebook/init.js +++ b/packages/oae-authentication/lib/strategies/facebook/init.js @@ -13,29 +13,28 @@ * permissions and limitations under the License. */ -const FacebookStrategy = require('passport-facebook').Strategy; +import passport from 'passport-facebook'; -const ConfigAPI = require('oae-config'); -const log = require('oae-logger').logger('oae-authentication'); +import * as ConfigAPI from 'oae-config'; +import { logger } from 'oae-logger'; -const AuthenticationAPI = require('oae-authentication'); +import * as AuthenticationAPI from 'oae-authentication'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; -const AuthenticationConfig = ConfigAPI.config('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationUtil = require('oae-authentication/lib/util'); +const FacebookStrategy = passport.Strategy; +const log = logger('oae-authentication'); -module.exports = function() { +const AuthenticationConfig = ConfigAPI.setUpConfig('oae-authentication'); + +export default function() { const strategy = {}; /** * @see oae-authentication/lib/strategy#shouldBeEnabled */ strategy.shouldBeEnabled = function(tenantAlias) { - return AuthenticationConfig.getValue( - tenantAlias, - AuthenticationConstants.providers.FACEBOOK, - 'enabled' - ); + return AuthenticationConfig.getValue(tenantAlias, AuthenticationConstants.providers.FACEBOOK, 'enabled'); }; /** @@ -43,11 +42,7 @@ module.exports = function() { */ strategy.getPassportStrategy = function(tenant) { // We fetch the config values *in* the getPassportStrategy so it can be re-configured at run-time. - const clientID = AuthenticationConfig.getValue( - tenant.alias, - AuthenticationConstants.providers.FACEBOOK, - 'appid' - ); + const clientID = AuthenticationConfig.getValue(tenant.alias, AuthenticationConstants.providers.FACEBOOK, 'appid'); const clientSecret = AuthenticationConfig.getValue( tenant.alias, AuthenticationConstants.providers.FACEBOOK, @@ -60,10 +55,7 @@ module.exports = function() { clientSecret, passReqToCallback: true, profileFields: ['id', 'displayName', 'photos', 'emails'], - callbackURL: AuthenticationUtil.constructCallbackUrl( - tenant, - AuthenticationConstants.providers.FACEBOOK - ) + callbackURL: AuthenticationUtil.constructCallbackUrl(tenant, AuthenticationConstants.providers.FACEBOOK) }, (req, accessToken, refreshToken, profile, done) => { log().trace({ tenant, profile }, 'Received Facebook authentication callback'); @@ -98,4 +90,4 @@ module.exports = function() { // Register our strategy. AuthenticationAPI.registerStrategy(AuthenticationConstants.providers.FACEBOOK, strategy); -}; +} diff --git a/packages/oae-authentication/lib/strategies/facebook/rest.js b/packages/oae-authentication/lib/strategies/facebook/rest.js index b26e0fb2b5..4efe361f36 100644 --- a/packages/oae-authentication/lib/strategies/facebook/rest.js +++ b/packages/oae-authentication/lib/strategies/facebook/rest.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -const OAE = require('oae-util/lib/oae'); +import * as OAE from 'oae-util/lib/oae'; -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationUtil = require('oae-authentication/lib/util'); +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; /** * @REST postAuthFacebook @@ -32,10 +32,7 @@ const AuthenticationUtil = require('oae-authentication/lib/util'); */ OAE.tenantRouter.on('post', '/api/auth/facebook', (req, res, next) => { // Get the ID under which we registered this strategy for this tenant - const strategyId = AuthenticationUtil.getStrategyId( - req.tenant, - AuthenticationConstants.providers.FACEBOOK - ); + const strategyId = AuthenticationUtil.getStrategyId(req.tenant, AuthenticationConstants.providers.FACEBOOK); // Perform the initial authentication step AuthenticationUtil.handleExternalSetup(strategyId, { scope: ['email'] }, req, res, next); @@ -54,11 +51,10 @@ OAE.tenantRouter.on('post', '/api/auth/facebook', (req, res, next) => { */ OAE.tenantRouter.on('get', '/api/auth/facebook/callback', (req, res, next) => { // Get the ID under which we registered this strategy for this tenant - const strategyId = AuthenticationUtil.getStrategyId( - req.tenant, - AuthenticationConstants.providers.FACEBOOK - ); + const strategyId = AuthenticationUtil.getStrategyId(req.tenant, AuthenticationConstants.providers.FACEBOOK); // Log the user in AuthenticationUtil.handleExternalCallback(strategyId, req, res, next); }); + +export default OAE; diff --git a/packages/oae-authentication/lib/strategies/google/init.js b/packages/oae-authentication/lib/strategies/google/init.js index 596d8bf9db..b9644a9e42 100644 --- a/packages/oae-authentication/lib/strategies/google/init.js +++ b/packages/oae-authentication/lib/strategies/google/init.js @@ -13,31 +13,30 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); -const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; +import util from 'util'; +import _ from 'underscore'; +import passport from 'passport-google-oauth'; -const ConfigAPI = require('oae-config'); -const log = require('oae-logger').logger('oae-authentication'); +import * as ConfigAPI from 'oae-config'; +import { logger } from 'oae-logger'; -const AuthenticationAPI = require('oae-authentication'); +import * as AuthenticationAPI from 'oae-authentication'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; -const AuthenticationConfig = ConfigAPI.config('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationUtil = require('oae-authentication/lib/util'); +const GoogleStrategy = passport.OAuth2Strategy; +const log = logger('oae-authentication'); -module.exports = function() { +const AuthenticationConfig = ConfigAPI.setUpConfig('oae-authentication'); + +export default function() { const strategy = {}; /** * @see oae-authentication/lib/strategy#shouldBeEnabled */ strategy.shouldBeEnabled = function(tenantAlias) { - return AuthenticationConfig.getValue( - tenantAlias, - AuthenticationConstants.providers.GOOGLE, - 'enabled' - ); + return AuthenticationConfig.getValue(tenantAlias, AuthenticationConstants.providers.GOOGLE, 'enabled'); }; /** @@ -45,16 +44,8 @@ module.exports = function() { */ strategy.getPassportStrategy = function(tenant) { // We fetch the config values *in* the getPassportStrategy so it can be re-configured at run-time. - const key = AuthenticationConfig.getValue( - tenant.alias, - AuthenticationConstants.providers.GOOGLE, - 'key' - ); - const secret = AuthenticationConfig.getValue( - tenant.alias, - AuthenticationConstants.providers.GOOGLE, - 'secret' - ); + const key = AuthenticationConfig.getValue(tenant.alias, AuthenticationConstants.providers.GOOGLE, 'key'); + const secret = AuthenticationConfig.getValue(tenant.alias, AuthenticationConstants.providers.GOOGLE, 'secret'); let domains = AuthenticationConfig.getValue( tenant.alias, AuthenticationConstants.providers.GOOGLE, @@ -75,68 +66,62 @@ module.exports = function() { clientSecret: secret, passReqToCallback: true, scope: ['profile', 'email'], - callbackURL: AuthenticationUtil.constructCallbackUrl( - tenant, - AuthenticationConstants.providers.GOOGLE - ) + callbackURL: AuthenticationUtil.constructCallbackUrl(tenant, AuthenticationConstants.providers.GOOGLE) }; - const passportStrategy = new GoogleStrategy( - options, - (req, accessToken, refreshToken, profile, done) => { - log().trace( - { - tenant, - profile - }, - 'Received Google authentication callback' - ); - - const email = profile.emails[0].value; - - // Ensure the email belongs to a domain we allow - if (!_.isEmpty(domains)) { - const emailDomain = email.split('@')[1]; - if (!_.contains(domains, emailDomain)) { - const err = { - code: 400, - msg: util.format( - 'You tried to sign in with an email address that belongs to a domain (%s) that is not allowed access', - emailDomain - ), - reason: 'domain_not_allowed' - }; - return done(err); - } - } - - // Re-use the email address as the externalId - const externalId = email; - const { displayName } = profile; - - // We ignore the locale returned by google because it only specifies - // the language, but not the region which isn't very useful - const opts = { email }; - const { picture } = profile._json; - if (picture) { - opts.smallPictureUri = 'remote:' + picture; - opts.mediumPictureUri = 'remote:' + picture; + const passportStrategy = new GoogleStrategy(options, (req, accessToken, refreshToken, profile, done) => { + log().trace( + { + tenant, + profile + }, + 'Received Google authentication callback' + ); + + const email = profile.emails[0].value; + + // Ensure the email belongs to a domain we allow + if (!_.isEmpty(domains)) { + const emailDomain = email.split('@')[1]; + if (!_.contains(domains, emailDomain)) { + const err = { + code: 400, + msg: util.format( + 'You tried to sign in with an email address that belongs to a domain (%s) that is not allowed access', + emailDomain + ), + reason: 'domain_not_allowed' + }; + return done(err); } + } - AuthenticationUtil.handleExternalGetOrCreateUser( - req, - AuthenticationConstants.providers.GOOGLE, - externalId, - null, - displayName, - opts, - done - ); + // Re-use the email address as the externalId + const externalId = email; + const { displayName } = profile; + + // We ignore the locale returned by google because it only specifies + // the language, but not the region which isn't very useful + const opts = { email }; + const { picture } = profile._json; + if (picture) { + opts.smallPictureUri = 'remote:' + picture; + opts.mediumPictureUri = 'remote:' + picture; } - ); + + AuthenticationUtil.handleExternalGetOrCreateUser( + req, + AuthenticationConstants.providers.GOOGLE, + externalId, + null, + displayName, + opts, + done + ); + }); return passportStrategy; }; // Register our strategy. AuthenticationAPI.registerStrategy(AuthenticationConstants.providers.GOOGLE, strategy); -}; +} diff --git a/packages/oae-authentication/lib/strategies/google/rest.js b/packages/oae-authentication/lib/strategies/google/rest.js index 58f6b9a831..e4ea1845a8 100644 --- a/packages/oae-authentication/lib/strategies/google/rest.js +++ b/packages/oae-authentication/lib/strategies/google/rest.js @@ -13,12 +13,12 @@ * permissions and limitations under the License. */ -const ConfigAPI = require('oae-config'); -const OAE = require('oae-util/lib/oae'); +import * as ConfigAPI from 'oae-config'; +import * as OAE from 'oae-util/lib/oae'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; -const AuthenticationConfig = ConfigAPI.config('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationUtil = require('oae-authentication/lib/util'); +const AuthenticationConfig = ConfigAPI.setUpConfig('oae-authentication'); /** * @REST postAuthGoogle @@ -34,10 +34,7 @@ const AuthenticationUtil = require('oae-authentication/lib/util'); */ OAE.tenantRouter.on('post', '/api/auth/google', (req, res, next) => { // Get the ID under which we registered this strategy for this tenant - const strategyId = AuthenticationUtil.getStrategyId( - req.tenant, - AuthenticationConstants.providers.GOOGLE - ); + const strategyId = AuthenticationUtil.getStrategyId(req.tenant, AuthenticationConstants.providers.GOOGLE); const options = { // To avoid authenticating with the wrong Google account, we give the user the opportunity to select or add @@ -74,11 +71,10 @@ OAE.tenantRouter.on('post', '/api/auth/google', (req, res, next) => { */ OAE.tenantRouter.on('get', '/api/auth/google/callback', (req, res, next) => { // Get the ID under which we registered this strategy for this tenant - const strategyId = AuthenticationUtil.getStrategyId( - req.tenant, - AuthenticationConstants.providers.GOOGLE - ); + const strategyId = AuthenticationUtil.getStrategyId(req.tenant, AuthenticationConstants.providers.GOOGLE); // Log the user in AuthenticationUtil.handleExternalCallback(strategyId, req, res, next); }); + +export default OAE; diff --git a/packages/oae-authentication/lib/strategies/ldap/init.js b/packages/oae-authentication/lib/strategies/ldap/init.js index 00e51a8354..f5449b0064 100644 --- a/packages/oae-authentication/lib/strategies/ldap/init.js +++ b/packages/oae-authentication/lib/strategies/ldap/init.js @@ -13,18 +13,21 @@ * permissions and limitations under the License. */ -const LDAPStrategy = require('passport-ldapauth').Strategy; +import passport from 'passport-ldapauth'; -const ConfigAPI = require('oae-config'); -const { Context } = require('oae-context'); -const log = require('oae-logger').logger('oae-authentication'); +import * as ConfigAPI from 'oae-config'; +import { Context } from 'oae-context'; +import { logger } from 'oae-logger'; -const AuthenticationAPI = require('oae-authentication'); +import * as AuthenticationAPI from 'oae-authentication'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; -const AuthenticationConfig = ConfigAPI.config('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); +const LDAPStrategy = passport.Strategy; +const log = logger('oae-authentication'); -module.exports = function() { +const AuthenticationConfig = ConfigAPI.setUpConfig('oae-authentication'); + +export default function() { const strategy = {}; /** @@ -86,6 +89,7 @@ module.exports = function() { opts.emailVerified = true; } } + if (mapLocale) { opts.locale = profile[mapLocale]; } @@ -106,4 +110,4 @@ module.exports = function() { // Register our strategy. AuthenticationAPI.registerStrategy(AuthenticationConstants.providers.LDAP, strategy); -}; +} diff --git a/packages/oae-authentication/lib/strategies/ldap/rest.js b/packages/oae-authentication/lib/strategies/ldap/rest.js index c61b8dd5d1..535fb43bcd 100644 --- a/packages/oae-authentication/lib/strategies/ldap/rest.js +++ b/packages/oae-authentication/lib/strategies/ldap/rest.js @@ -13,12 +13,12 @@ * permissions and limitations under the License. */ -const passport = require('passport'); +import passport from 'passport'; -const OAE = require('oae-util/lib/oae'); +import * as OAE from 'oae-util/lib/oae'; -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationUtil = require('oae-authentication/lib/util'); +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; /** * Log in using LDAP @@ -28,10 +28,7 @@ const AuthenticationUtil = require('oae-authentication/lib/util'); * @api private */ const _handleLDAPAuthentication = function(req, res, next) { - const strategyId = AuthenticationUtil.getStrategyId( - req.tenant, - AuthenticationConstants.providers.LDAP - ); + const strategyId = AuthenticationUtil.getStrategyId(req.tenant, AuthenticationConstants.providers.LDAP); const errorHandler = AuthenticationUtil.handlePassportError(req, res, next); passport.authenticate(strategyId)(req, res, errorHandler); }; @@ -52,3 +49,5 @@ OAE.tenantRouter.on('post', '/api/auth/ldap', _handleLDAPAuthentication, (req, r // This callback only gets called when we log in succesfully. return res.status(200).send(req.ctx.user()); }); + +export default OAE; diff --git a/packages/oae-authentication/lib/strategies/local/init.js b/packages/oae-authentication/lib/strategies/local/init.js index 38f673fe8e..a3761e121c 100644 --- a/packages/oae-authentication/lib/strategies/local/init.js +++ b/packages/oae-authentication/lib/strategies/local/init.js @@ -13,23 +13,25 @@ * permissions and limitations under the License. */ -const LocalStrategy = require('passport-local').Strategy; -const passport = require('passport'); +import passportLocal from 'passport-local'; +import passport from 'passport'; -const ConfigAPI = require('oae-config'); -const { Context } = require('oae-context'); -const PrincipalsAPI = require('oae-principals'); -const { User } = require('oae-principals/lib/model'); +import * as ConfigAPI from 'oae-config'; +import { Context } from 'oae-context'; +import * as PrincipalsAPI from 'oae-principals'; +import { User } from 'oae-principals/lib/model'; -const AuthenticationAPI = require('oae-authentication'); +import * as AuthenticationAPI from 'oae-authentication'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; -const AuthenticationConfig = ConfigAPI.config('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationUtil = require('oae-authentication/lib/util'); +const LocalStrategy = passportLocal.Strategy; + +const AuthenticationConfig = ConfigAPI.setUpConfig('oae-authentication'); let globalTenantAlias = null; -module.exports = function(config) { +export default function(config) { globalTenantAlias = config.servers.globalAdminAlias; // Build up the OAE strategy. @@ -45,58 +47,47 @@ module.exports = function(config) { // Otherwise we need to check the configuration. } - return AuthenticationConfig.getValue( - tenantAlias, - AuthenticationConstants.providers.LOCAL, - 'enabled' - ); + + return AuthenticationConfig.getValue(tenantAlias, AuthenticationConstants.providers.LOCAL, 'enabled'); }; /** * @see oae-authentication/lib/strategy#getPassportStrategy */ strategy.getPassportStrategy = function() { - const passportStrategy = new LocalStrategy( - { passReqToCallback: true }, - (req, username, password, done) => { - const { tenant } = req; - - AuthenticationAPI.checkPassword(tenant.alias, username, password, (err, userId) => { - if (err && err.code === 401) { - // The provided password was incorrect - return done(null, false); - } + const passportStrategy = new LocalStrategy({ passReqToCallback: true }, (req, username, password, done) => { + const { tenant } = req; + + AuthenticationAPI.checkPassword(tenant.alias, username, password, (err, userId) => { + if (err && err.code === 401) { + // The provided password was incorrect + return done(null, false); + } + + if (err) { + // Some internal error occurred + return done(err); + } + + // By this point we know that we were succesfully logged in. Retrieve + // the user account and stick it in the context. + const ctx = new Context(tenant, new User(tenant.alias, userId)); + PrincipalsAPI.getUser(ctx, userId, (err, user) => { if (err) { - // Some internal error occurred return done(err); } - // By this point we know that we were succesfully logged in. Retrieve - // the user account and stick it in the context. - const ctx = new Context(tenant, new User(tenant.alias, userId)); - PrincipalsAPI.getUser(ctx, userId, (err, user) => { - if (err) { - return done(err); - } - if (user.deleted) { - return done(null, false); - } - - const strategyId = AuthenticationUtil.getStrategyId( - tenant, - AuthenticationConstants.providers.LOCAL - ); - const authObj = { user, strategyId }; - AuthenticationUtil.logAuthenticationSuccess( - req, - authObj, - AuthenticationConstants.providers.LOCAL - ); - return done(null, authObj); - }); + if (user.deleted) { + return done(null, false); + } + + const strategyId = AuthenticationUtil.getStrategyId(tenant, AuthenticationConstants.providers.LOCAL); + const authObj = { user, strategyId }; + AuthenticationUtil.logAuthenticationSuccess(req, authObj, AuthenticationConstants.providers.LOCAL); + return done(null, authObj); }); - } - ); + }); + }); return passportStrategy; }; @@ -112,4 +103,4 @@ module.exports = function(config) { AuthenticationConstants.providers.LOCAL ); passport.use(adminLocalPassportStrategyName, strategy.getPassportStrategy(globalTenant)); -}; +} diff --git a/packages/oae-authentication/lib/strategies/local/rest.js b/packages/oae-authentication/lib/strategies/local/rest.js index 2c861f8559..58e39fb4b2 100644 --- a/packages/oae-authentication/lib/strategies/local/rest.js +++ b/packages/oae-authentication/lib/strategies/local/rest.js @@ -13,13 +13,13 @@ * permissions and limitations under the License. */ -const passport = require('passport'); +import passport from 'passport'; -const OAE = require('oae-util/lib/oae'); +import * as OAE from 'oae-util/lib/oae'; -const AuthenticationAPI = require('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationUtil = require('oae-authentication/lib/util'); +import * as AuthenticationAPI from 'oae-authentication'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; /** * @REST postAuthLogin @@ -36,10 +36,7 @@ const AuthenticationUtil = require('oae-authentication/lib/util'); * @HttpResponse 401 Unauthorized */ const _handleLocalAuthentication = function(req, res, next) { - const strategyId = AuthenticationUtil.getStrategyId( - req.tenant, - AuthenticationConstants.providers.LOCAL - ); + const strategyId = AuthenticationUtil.getStrategyId(req.tenant, AuthenticationConstants.providers.LOCAL); const errorHandler = AuthenticationUtil.handlePassportError(req, res, next); passport.authenticate(strategyId)(req, res, errorHandler); }; @@ -57,14 +54,8 @@ const _handleLocalAuthenticationSuccess = function(req, res) { res.status(200).send(req.oaeAuthInfo.user); }; -OAE.globalAdminRouter.on('post', '/api/auth/login', [ - _handleLocalAuthentication, - _handleLocalAuthenticationSuccess -]); -OAE.tenantRouter.on('post', '/api/auth/login', [ - _handleLocalAuthentication, - _handleLocalAuthenticationSuccess -]); +OAE.globalAdminRouter.on('post', '/api/auth/login', [_handleLocalAuthentication, _handleLocalAuthenticationSuccess]); +OAE.tenantRouter.on('post', '/api/auth/login', [_handleLocalAuthentication, _handleLocalAuthenticationSuccess]); /** * @REST postUserIdPassword @@ -88,19 +79,13 @@ OAE.tenantRouter.on('post', '/api/auth/login', [ * @HttpResponse 401 You're not authorized to change this user's password */ const _handleChangePassword = function(req, res) { - AuthenticationAPI.changePassword( - req.ctx, - req.params.userId, - req.body.oldPassword, - req.body.newPassword, - err => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.sendStatus(200); + AuthenticationAPI.changePassword(req.ctx, req.params.userId, req.body.oldPassword, req.body.newPassword, err => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + return res.sendStatus(200); + }); }; OAE.globalAdminRouter.on('post', '/api/user/:userId/password', _handleChangePassword); @@ -117,22 +102,18 @@ OAE.tenantRouter.on('post', '/api/user/:userId/password', _handleChangePassword) * @api private */ const _handleLocalUsernameExists = function(req, res) { - AuthenticationAPI.localUsernameExists( - req.ctx, - req.params.tenantAlias, - req.params.username, - (err, exists) => { - if (err) { - return res.status(err.code).send(err.msg); - } + AuthenticationAPI.localUsernameExists(req.ctx, req.params.tenantAlias, req.params.username, (err, exists) => { + if (err) { + return res.status(err.code).send(err.msg); + } - // If the login id doesn't exist, we send back a 404 - if (exists) { - return res.sendStatus(200); - } - return res.sendStatus(404); + // If the login id doesn't exist, we send back a 404 + if (exists) { + return res.sendStatus(200); } - ); + + return res.sendStatus(404); + }); }; /** @@ -150,11 +131,7 @@ const _handleLocalUsernameExists = function(req, res) { * @HttpResponse 400 Please specify a username * @HttpResponse 404 Username does not exist */ -OAE.globalAdminRouter.on( - 'get', - '/api/auth/:tenantAlias/exists/:username', - _handleLocalUsernameExists -); +OAE.globalAdminRouter.on('get', '/api/auth/:tenantAlias/exists/:username', _handleLocalUsernameExists); /** * @REST getAuthExistsUsername @@ -171,3 +148,5 @@ OAE.globalAdminRouter.on( * @HttpResponse 404 Username does not exist */ OAE.tenantRouter.on('get', '/api/auth/exists/:username', _handleLocalUsernameExists); + +export default OAE; diff --git a/packages/oae-authentication/lib/strategies/oauth/api.js b/packages/oae-authentication/lib/strategies/oauth/api.js index 5d556c8111..57ba212caa 100644 --- a/packages/oae-authentication/lib/strategies/oauth/api.js +++ b/packages/oae-authentication/lib/strategies/oauth/api.js @@ -13,12 +13,12 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const { Validator } = require('oae-util/lib/validator'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import { Validator } from 'oae-util/lib/validator'; -const OAuthDAO = require('./internal/dao'); +import * as OAuthDAO from './internal/dao'; /// ////////// // Clients // @@ -35,9 +35,7 @@ const OAuthDAO = require('./internal/dao'); */ const getClients = function(ctx, userId, callback) { const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'Anonymous users do not have clients' }) - .isLoggedInUser(ctx); + validator.check(null, { code: 401, msg: 'Anonymous users do not have clients' }).isLoggedInUser(ctx); validator.check(userId, { code: 400, msg: 'An invalid userId was passed in' }).isUserId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); @@ -71,9 +69,7 @@ const getClients = function(ctx, userId, callback) { */ const createClient = function(ctx, userId, displayName, callback) { const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'Anonymous users cannot create a client' }) - .isLoggedInUser(ctx); + validator.check(null, { code: 401, msg: 'Anonymous users cannot create a client' }).isLoggedInUser(ctx); validator.check(userId, { code: 400, msg: 'A client must be bound to a user' }).isUserId(); validator.check(displayName, { code: 400, msg: 'Missing client displayName' }).notEmpty(); if (validator.hasErrors()) { @@ -105,13 +101,12 @@ const createClient = function(ctx, userId, displayName, callback) { */ const updateClient = function(ctx, clientId, displayName, secret, callback) { const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'Anonymous users cannot create a client' }) - .isLoggedInUser(ctx); + validator.check(null, { code: 401, msg: 'Anonymous users cannot create a client' }).isLoggedInUser(ctx); validator.check(clientId, { code: 400, msg: 'Missing client id' }).notEmpty(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } + if (!displayName && !secret) { return callback({ code: 400, msg: 'A displayName and/or secret has to be provided' }); } @@ -121,6 +116,7 @@ const updateClient = function(ctx, clientId, displayName, secret, callback) { if (err) { return callback(err); } + if (!client) { return callback({ code: 404, msg: 'No client with that id was found' }); } @@ -154,9 +150,7 @@ const updateClient = function(ctx, clientId, displayName, secret, callback) { */ const deleteClient = function(ctx, clientId, callback) { const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'Anonymous users cannot delete a client' }) - .isLoggedInUser(ctx); + validator.check(null, { code: 401, msg: 'Anonymous users cannot delete a client' }).isLoggedInUser(ctx); validator.check(clientId, { code: 400, msg: 'Missing client id' }).notEmpty(); if (validator.hasErrors()) { return callback(validator.getFirstError()); @@ -167,6 +161,7 @@ const deleteClient = function(ctx, clientId, callback) { if (err) { return callback(err); } + if (!client) { return callback({ code: 404, msg: 'No client with that id was found' }); } @@ -196,15 +191,14 @@ const generateToken = function(length) { for (let i = 0; i < length; i++) { randomString += chars[_.random(0, chars.length - 1)]; } + return randomString; }; -module.exports = { - Clients: { - getClients, - createClient, - updateClient, - deleteClient - }, - generateToken +const Clients = { + getClients, + createClient, + updateClient, + deleteClient }; +export { Clients, generateToken }; diff --git a/packages/oae-authentication/lib/strategies/oauth/init.js b/packages/oae-authentication/lib/strategies/oauth/init.js index 324cd5ab5e..e1f170fb71 100644 --- a/packages/oae-authentication/lib/strategies/oauth/init.js +++ b/packages/oae-authentication/lib/strategies/oauth/init.js @@ -13,32 +13,36 @@ * permissions and limitations under the License. */ -const BearerStrategy = require('passport-http-bearer').Strategy; -const passport = require('passport'); +import passportBearer from 'passport-http-bearer'; +import passport from 'passport'; -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationUtil = require('oae-authentication/lib/util'); -const OAE = require('oae-util/lib/oae'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const Telemetry = require('oae-telemetry').telemetry('oauth'); +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; +import * as OAE from 'oae-util/lib/oae'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import { telemetry } from 'oae-telemetry'; -const OAuthDAO = require('./internal/dao'); +import * as OAuthDAO from './internal/dao'; + +const BearerStrategy = passportBearer.Strategy; +const Telemetry = telemetry('oauth'); // Used to check if the authorization header starts with "Bearer " const BEARER_REGEX = /^Bearer /i; -module.exports = function() { +export default function() { /*! - * This strategy is used to authenticate users based on an access token (aka a bearer token). - * - * @see http://tools.ietf.org/html/rfc6750 - */ + * This strategy is used to authenticate users based on an access token (aka a bearer token). + * + * @see http://tools.ietf.org/html/rfc6750 + */ passport.use( new BearerStrategy((accessToken, callback) => { OAuthDAO.AccessTokens.getAccessToken(accessToken, (err, token) => { if (err) { return callback(err); } + if (!token) { return callback(null, false); } @@ -48,6 +52,7 @@ module.exports = function() { if (err && err.code === 404) { return callback(null, false); } + if (err) { return callback(err); } @@ -60,10 +65,10 @@ module.exports = function() { ); /*! - * This middleware will apply "OAuth: Bearer Token" authentication if it detects - * that there is a token in the request. This needs to run before any other middleware that does something with - * the user, as this middleware will put the `user` object on the request. - */ + * This middleware will apply "OAuth: Bearer Token" authentication if it detects + * that there is a token in the request. This needs to run before any other middleware that does something with + * the user, as this middleware will put the `user` object on the request. + */ OAE.tenantServer.use((req, res, next) => { if (!_hasAccessToken(req)) { // Don't invoke the OAuth workflow if there is no OAuth access token @@ -90,7 +95,7 @@ module.exports = function() { return next(); }); }); -}; +} /** * Find an OAuth access token in the HTTP request. diff --git a/packages/oae-authentication/lib/strategies/oauth/internal/dao.accesstokens.js b/packages/oae-authentication/lib/strategies/oauth/internal/dao.accesstokens.js index a039aea517..e7b81ee4d8 100644 --- a/packages/oae-authentication/lib/strategies/oauth/internal/dao.accesstokens.js +++ b/packages/oae-authentication/lib/strategies/oauth/internal/dao.accesstokens.js @@ -13,11 +13,11 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const Cassandra = require('oae-util/lib/cassandra'); +import * as Cassandra from 'oae-util/lib/cassandra'; -const { AccessToken } = require('../model'); +import { AccessToken } from '../model'; /** * Creates an access token @@ -36,12 +36,7 @@ const createAccessToken = function(token, userId, clientId, callback) { userId, clientId }), - Cassandra.constructUpsertCQL( - 'OAuthAccessTokenByUser', - ['userId', 'clientId'], - [userId, clientId], - { token } - ) + Cassandra.constructUpsertCQL('OAuthAccessTokenByUser', ['userId', 'clientId'], [userId, clientId], { token }) ]; Cassandra.runBatchQuery(queries, err => { @@ -67,6 +62,7 @@ const getAccessToken = function(token, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback(null, null); } @@ -95,6 +91,7 @@ const getAccessTokenForUserAndClient = function(userId, clientId, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback(null, null); } @@ -106,8 +103,4 @@ const getAccessTokenForUserAndClient = function(userId, clientId, callback) { ); }; -module.exports = { - createAccessToken, - getAccessToken, - getAccessTokenForUserAndClient -}; +export { createAccessToken, getAccessToken, getAccessTokenForUserAndClient }; diff --git a/packages/oae-authentication/lib/strategies/oauth/internal/dao.clients.js b/packages/oae-authentication/lib/strategies/oauth/internal/dao.clients.js index e32635d227..06c2e779d2 100644 --- a/packages/oae-authentication/lib/strategies/oauth/internal/dao.clients.js +++ b/packages/oae-authentication/lib/strategies/oauth/internal/dao.clients.js @@ -13,11 +13,11 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const Cassandra = require('oae-util/lib/cassandra'); +import * as Cassandra from 'oae-util/lib/cassandra'; -const { Client } = require('../model'); +import { Client } from '../model'; /** * Creates a client. @@ -105,6 +105,7 @@ const getClientById = function(id, callback) { if (err) { return callback(err); } + if (_.isEmpty(clients)) { return callback(); } @@ -122,21 +123,17 @@ const getClientById = function(id, callback) { * @param {Client[]} callback.clients The set of clients that are registered for this user */ const getClientsByUser = function(userId, callback) { - Cassandra.runQuery( - 'SELECT "clientId" FROM "OAuthClientsByUser" WHERE "userId" = ?', - [userId], - (err, rows) => { - if (err) { - return callback(err); - } - - const clientIds = _.map(rows, row => { - return row.get('clientId'); - }); - - _getClientsByIds(clientIds, callback); + Cassandra.runQuery('SELECT "clientId" FROM "OAuthClientsByUser" WHERE "userId" = ?', [userId], (err, rows) => { + if (err) { + return callback(err); } - ); + + const clientIds = _.map(rows, row => { + return row.get('clientId'); + }); + + _getClientsByIds(clientIds, callback); + }); }; /** @@ -169,10 +166,4 @@ const _getClientsByIds = function(clientIds, callback) { }); }; -module.exports = { - createClient, - updateClient, - deleteClient, - getClientById, - getClientsByUser -}; +export { createClient, updateClient, deleteClient, getClientById, getClientsByUser }; diff --git a/packages/oae-authentication/lib/strategies/oauth/internal/dao.js b/packages/oae-authentication/lib/strategies/oauth/internal/dao.js index bfb5b0a4c6..aea5aff83f 100644 --- a/packages/oae-authentication/lib/strategies/oauth/internal/dao.js +++ b/packages/oae-authentication/lib/strategies/oauth/internal/dao.js @@ -13,10 +13,7 @@ * permissions and limitations under the License. */ -const AccessTokens = require('./dao.accesstokens'); -const Clients = require('./dao.clients'); +import * as AccessTokens from './dao.accesstokens'; +import * as Clients from './dao.clients'; -module.exports = { - AccessTokens, - Clients -}; +export { AccessTokens, Clients }; diff --git a/packages/oae-authentication/lib/strategies/oauth/model.js b/packages/oae-authentication/lib/strategies/oauth/model.js index 51be26dd2f..eeaf7ebdf3 100644 --- a/packages/oae-authentication/lib/strategies/oauth/model.js +++ b/packages/oae-authentication/lib/strategies/oauth/model.js @@ -45,7 +45,4 @@ const Client = function(id, displayName, secret, userId) { return that; }; -module.exports = { - AccessToken, - Client -}; +export { AccessToken, Client }; diff --git a/packages/oae-authentication/lib/strategies/oauth/rest.js b/packages/oae-authentication/lib/strategies/oauth/rest.js index 1cafe0270c..537256f476 100644 --- a/packages/oae-authentication/lib/strategies/oauth/rest.js +++ b/packages/oae-authentication/lib/strategies/oauth/rest.js @@ -13,17 +13,21 @@ * permissions and limitations under the License. */ -const { BasicStrategy } = require('passport-http'); -const ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy; -const oauth2orize = require('oauth2orize'); -const passport = require('passport'); +import { BasicStrategy } from 'passport-http'; +import OAuthPassport from 'passport-oauth2-client-password'; +import oauth2orize from 'oauth2orize'; +import passport from 'passport'; -const log = require('oae-logger').logger('oae-authentication'); -const OAE = require('oae-util/lib/oae'); -const OaeServer = require('oae-util/lib/server'); +import { logger } from 'oae-logger'; -const OAuthDAO = require('./internal/dao'); -const OAuthAPI = require('./api'); +import * as OAE from 'oae-util/lib/oae'; +import * as OaeServer from 'oae-util/lib/server'; + +import * as OAuthDAO from './internal/dao'; +import * as OAuthAPI from './api'; + +const ClientPasswordStrategy = OAuthPassport.Strategy; +const log = logger('oae-authentication'); /// ////////////// // OAuth setup // @@ -55,42 +59,36 @@ const server = oauth2orize.createServer(); * @param {AccessToken} callback.token An access token that can be used to interact with the OAE apis as a user */ server.exchange( - oauth2orize.exchange.clientCredentials( - { userProperty: 'oaeAuthInfo' }, - (client, scope, callback) => { - // In theory, each client should cache their access token, but that's probably a pipedream - // We should check if this client has a token already so we don't generate a new one each time - OAuthDAO.AccessTokens.getAccessTokenForUserAndClient( - client.userId, - client.id, - (err, accessToken) => { - if (err) { - return callback(err); - - // This client has a token, return it - } - if (accessToken) { - return callback(null, accessToken.token); - } - - // This is the first time this client is requesting a token, we'll need to generate one - const token = OAuthAPI.generateToken(256); - OAuthDAO.AccessTokens.createAccessToken(token, client.userId, client.id, err => { - if (err) { - return callback(err); - } - - // Return an access token to the client - log().info( - { client: client.id, user: client.userId }, - 'An access token has been handed out via Client Credentials' - ); - return callback(null, token); - }); + oauth2orize.exchange.clientCredentials({ userProperty: 'oaeAuthInfo' }, (client, scope, callback) => { + // In theory, each client should cache their access token, but that's probably a pipedream + // We should check if this client has a token already so we don't generate a new one each time + OAuthDAO.AccessTokens.getAccessTokenForUserAndClient(client.userId, client.id, (err, accessToken) => { + if (err) { + return callback(err); + + // This client has a token, return it + } + + if (accessToken) { + return callback(null, accessToken.token); + } + + // This is the first time this client is requesting a token, we'll need to generate one + const token = OAuthAPI.generateToken(256); + OAuthDAO.AccessTokens.createAccessToken(token, client.userId, client.id, err => { + if (err) { + return callback(err); } - ); - } - ) + + // Return an access token to the client + log().info( + { client: client.id, user: client.userId }, + 'An access token has been handed out via Client Credentials' + ); + return callback(null, token); + }); + }); + }) ); /// /////////////// @@ -112,9 +110,11 @@ const verifyClientAuthentication = function(clientId, clientSecret, callback) { if (err) { return callback(err); } + if (!client) { return callback(null, false); } + if (client.secret !== clientSecret) { log().warn({ client: client.id }, 'A client attempted to authenticate with the wrong secret'); return callback(null, false); @@ -241,19 +241,13 @@ OAE.tenantRouter.on('get', '/api/auth/oauth/clients/:userId', (req, res) => { * @HttpResponse 401 Unauthorized */ OAE.tenantRouter.on('post', '/api/auth/oauth/clients/:userId/:clientId', (req, res) => { - OAuthAPI.Clients.updateClient( - req.ctx, - req.params.clientId, - req.body.displayName, - req.body.secret, - (err, client) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - res.status(200).send(client); + OAuthAPI.Clients.updateClient(req.ctx, req.params.clientId, req.body.displayName, req.body.secret, (err, client) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(200).send(client); + }); }); /** @@ -279,3 +273,5 @@ OAE.tenantRouter.on('delete', '/api/auth/oauth/clients/:userId/:clientId', (req, res.sendStatus(200); }); }); + +export default OAE; diff --git a/packages/oae-authentication/lib/strategies/shibboleth/api.js b/packages/oae-authentication/lib/strategies/shibboleth/api.js index 1c85f24a45..55af25e66e 100644 --- a/packages/oae-authentication/lib/strategies/shibboleth/api.js +++ b/packages/oae-authentication/lib/strategies/shibboleth/api.js @@ -13,15 +13,17 @@ * permissions and limitations under the License. */ -const util = require('util'); +import util from 'util'; -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const Signature = require('oae-util/lib/signature'); -const TenantsAPI = require('oae-tenants/lib/api'); -const { Validator } = require('oae-util/lib/validator'); +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as Signature from 'oae-util/lib/signature'; +import * as TenantsAPI from 'oae-tenants/lib/api'; +import { Validator } from 'oae-util/lib/validator'; -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationConfig = require('oae-config').config('oae-authentication'); +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import { setUpConfig } from 'oae-config'; + +const AuthenticationConfig = setUpConfig('oae-authentication'); let config = null; @@ -50,11 +52,7 @@ const getSPHost = function() { * @return {Boolean} `true` if the strategy is enabled, `false` otherwise */ const isEnabled = function(tenantAlias) { - return AuthenticationConfig.getValue( - tenantAlias, - AuthenticationConstants.providers.SHIBBOLETH, - 'enabled' - ); + return AuthenticationConfig.getValue(tenantAlias, AuthenticationConstants.providers.SHIBBOLETH, 'enabled'); }; /** @@ -95,9 +93,7 @@ const getServiceProviderUrl = function(ctx) { */ const validateInitiateParameters = function(tenantAlias, signature, expires, callback) { const validator = new Validator(); - validator - .check(tenantAlias, { code: 400, msg: 'Missing tenant alias parameter' }) - .notEmpty(tenantAlias); + validator.check(tenantAlias, { code: 400, msg: 'Missing tenant alias parameter' }).notEmpty(tenantAlias); validator.check(signature, { code: 400, msg: 'Missing signature parameter' }).notEmpty(signature); validator.check(expires, { code: 400, msg: 'Missing expires parameter' }).notEmpty(expires); validator.check(expires, { code: 400, msg: 'Invalid expires parameter' }).isNumeric(); @@ -199,6 +195,7 @@ const getUser = function(tenant, userId, signature, expires, callback) { if (err) { return callback(err); } + if (user.deleted) { return callback({ code: 401, @@ -211,7 +208,7 @@ const getUser = function(tenant, userId, signature, expires, callback) { }); }; -module.exports = { +export { refreshConfiguration, getSPHost, isEnabled, diff --git a/packages/oae-authentication/lib/strategies/shibboleth/init.js b/packages/oae-authentication/lib/strategies/shibboleth/init.js index aea4d0f976..d1da494afe 100644 --- a/packages/oae-authentication/lib/strategies/shibboleth/init.js +++ b/packages/oae-authentication/lib/strategies/shibboleth/init.js @@ -13,23 +13,25 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const { AuthzConstants } = require('oae-authz/lib/constants'); -const Cassandra = require('oae-util/lib/cassandra'); -const ConfigAPI = require('oae-config'); -const log = require('oae-logger').logger('oae-authentication'); +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as ConfigAPI from 'oae-config'; +import { logger } from 'oae-logger'; -const AuthenticationAPI = require('oae-authentication'); +import * as AuthenticationAPI from 'oae-authentication'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; -const AuthenticationConfig = ConfigAPI.config('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationUtil = require('oae-authentication/lib/util'); +import * as ShibbolethAPI from './api'; +import ShibbolethStrategy from './strategy'; -const ShibbolethAPI = require('./api'); -const ShibbolethStrategy = require('./strategy'); +const log = logger('oae-authentication'); -module.exports = function(config) { +const AuthenticationConfig = ConfigAPI.setUpConfig('oae-authentication'); + +export default function(config) { // Refresh the shibboleth configuration ShibbolethAPI.refreshConfiguration(config); @@ -73,17 +75,9 @@ module.exports = function(config) { // any of the configurable attributes. Rather than relying on `mod_shib`'s `remote_user` // attribute, we rely on the configured list as it allows administrators to specify // attributes on a per-tenant basis. We use `remote_user` as the fall back value - const externalId = _getBestAttributeValue( - tenant.alias, - 'externalIdAttributes', - headers, - headers.remote_user - ); + const externalId = _getBestAttributeValue(tenant.alias, 'externalIdAttributes', headers, headers.remote_user); if (!externalId) { - log().error( - { headers, tenant }, - 'No suitable attribute was found for the `externalId` attribute' - ); + log().error({ headers, tenant }, 'No suitable attribute was found for the `externalId` attribute'); return callback({ code: 500, msg: 'No suitable attribute was found for the `externalId` attribute' @@ -93,12 +87,7 @@ module.exports = function(config) { // There are a lot of SAML attributes that may indicate a user's display name. The administrator // should provide a suitable priority list to construct the display name. If no suitable value was // returned from the mapping, we fall back to the `remote_user` attribute, as this is always provided - const displayName = _getBestAttributeValue( - tenant.alias, - 'mapDisplayName', - headers, - headers.remote_user - ); + const displayName = _getBestAttributeValue(tenant.alias, 'mapDisplayName', headers, headers.remote_user); // Set the optional profile parameters const opts = { @@ -137,6 +126,7 @@ module.exports = function(config) { // There is no need to persist the metadata when the user account already exists } + if (!created) { return callback(null, user); } @@ -160,16 +150,16 @@ module.exports = function(config) { // We store extra information as it might be useful later on const metadata = { /* - * The Shib persistent ID is a triple of: - * * The IdP's entity ID - * * The SP's entity ID - * * A randomly generated ID identifying the user - * e.g.: https://idp.testshib.org/idp/shibboleth!https://shib-sp.oae-performance.oaeproject.org/shibboleth!wjsKmFPZ7Kjml9HqD0Dbio5vzVo= - * - * This ID can be used with the IdP to retrieve profile attributes of the user or to check if that user - * is still part of the organization. We store it for use it later on. - * @see https://wiki.shibboleth.net/confluence/display/SHIB2/IdPPersistentNameIdentifier - */ + * The Shib persistent ID is a triple of: + * * The IdP's entity ID + * * The SP's entity ID + * * A randomly generated ID identifying the user + * e.g.: https://idp.testshib.org/idp/shibboleth!https://shib-sp.oae-performance.oaeproject.org/shibboleth!wjsKmFPZ7Kjml9HqD0Dbio5vzVo= + * + * This ID can be used with the IdP to retrieve profile attributes of the user or to check if that user + * is still part of the organization. We store it for use it later on. + * @see https://wiki.shibboleth.net/confluence/display/SHIB2/IdPPersistentNameIdentifier + */ persistentId: headers['persistent-id'], // The entity ID of the IdP @@ -179,12 +169,7 @@ module.exports = function(config) { affiliation: headers.affiliation, unscopedAffiliation: headers['unscoped-affiliation'] }; - const q = Cassandra.constructUpsertCQL( - 'ShibbolethMetadata', - 'loginId', - loginId, - metadata - ); + const q = Cassandra.constructUpsertCQL('ShibbolethMetadata', 'loginId', loginId, metadata); if (!q) { log().error( { @@ -197,6 +182,7 @@ module.exports = function(config) { ); return callback({ code: 500, msg: 'Unable to store Shibboleth metadata' }); } + Cassandra.runQuery(q.query, q.parameters, err => { if (err) { return callback(err); @@ -213,7 +199,7 @@ module.exports = function(config) { // Register our strategy AuthenticationAPI.registerStrategy(AuthenticationConstants.providers.SHIBBOLETH, strategy); -}; +} /** * Get the value from the attribute that best matches a configured priority list diff --git a/packages/oae-authentication/lib/strategies/shibboleth/rest.js b/packages/oae-authentication/lib/strategies/shibboleth/rest.js index 2c0c6eaa2b..de5c303dbc 100644 --- a/packages/oae-authentication/lib/strategies/shibboleth/rest.js +++ b/packages/oae-authentication/lib/strategies/shibboleth/rest.js @@ -13,15 +13,17 @@ * permissions and limitations under the License. */ -const util = require('util'); -const passport = require('passport'); +import util from 'util'; +import passport from 'passport'; -const log = require('oae-logger').logger('shibboleth'); -const OAE = require('oae-util/lib/oae'); +import { logger } from 'oae-logger'; +import * as OAE from 'oae-util/lib/oae'; -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationUtil = require('oae-authentication/lib/util'); -const ShibbolethAPI = require('./api'); +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; +import * as ShibbolethAPI from './api'; + +const log = logger('shibboleth'); /** * @REST postAuthShibbolethTenant @@ -79,10 +81,7 @@ OAE.tenantRouter.on('get', '/api/auth/shibboleth/sp', (req, res, next) => { res.cookie('shibboleth', tenantAlias, { signed: true }); // Get the ID under which this strategy was registered for this tenant - const strategyId = AuthenticationUtil.getStrategyId( - tenant, - AuthenticationConstants.providers.SHIBBOLETH - ); + const strategyId = AuthenticationUtil.getStrategyId(tenant, AuthenticationConstants.providers.SHIBBOLETH); // Perform the initial authentication step AuthenticationUtil.handleExternalSetup(strategyId, null, req, res, next); @@ -127,10 +126,7 @@ OAE.tenantRouter.on('get', '/api/auth/shibboleth/sp/callback', (req, res) => { const tenantUrl = util.format('https://%s', tenant.host); // Get the Shibboleth strategy - const strategyId = AuthenticationUtil.getStrategyId( - tenant, - AuthenticationConstants.providers.SHIBBOLETH - ); + const strategyId = AuthenticationUtil.getStrategyId(tenant, AuthenticationConstants.providers.SHIBBOLETH); // Validate and authenticate the request passport.authenticate(strategyId, {}, (err, user, challenges, status) => { @@ -138,15 +134,13 @@ OAE.tenantRouter.on('get', '/api/auth/shibboleth/sp/callback', (req, res) => { log().error({ err, tenantAlias }, 'Error during Shibboleth authentication'); return res.redirect(tenantUrl + '/?authentication=failed&reason=error'); } + if (!user) { // The user's credentials didn't check out. This would rarely occur in a // normal situation as external auth providers don't usually redirect with // bad parameters in the request, so somebody is probably tampering with it. // We bail out immediately - log().warn( - { challenges, status }, - 'Possible tampering of external callback request detected' - ); + log().warn({ challenges, status }, 'Possible tampering of external callback request detected'); return res.redirect(tenantUrl + '/?authentication=failed&reason=tampering'); } @@ -186,10 +180,9 @@ OAE.tenantRouter.on('get', '/api/auth/shibboleth/callback', (req, res, next) => } // Log the user in - const strategyId = AuthenticationUtil.getStrategyId( - req.tenant, - AuthenticationConstants.providers.SHIBBOLETH - ); + const strategyId = AuthenticationUtil.getStrategyId(req.tenant, AuthenticationConstants.providers.SHIBBOLETH); return AuthenticationUtil.handleLogin(strategyId, user, req, res, next); }); }); + +export default OAE; diff --git a/packages/oae-authentication/lib/strategies/shibboleth/strategy.js b/packages/oae-authentication/lib/strategies/shibboleth/strategy.js index bfd779d61d..5aa858732a 100644 --- a/packages/oae-authentication/lib/strategies/shibboleth/strategy.js +++ b/packages/oae-authentication/lib/strategies/shibboleth/strategy.js @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -const util = require('util'); -const passport = require('passport'); +import util from 'util'; +import passport from 'passport'; /** * A Shibboleth passport authentication strategy @@ -54,12 +54,12 @@ Strategy.prototype.authenticate = function(req) { const self = this; /* - * If the user has authenticated through Shibboleth and is returning from the Identity Provider (IdP), - * there should be a `shib-session-id` header in the request. If the user is indicating that he - * wants to log in with Shibboleth, no such header will be present. - * - * It's up to the front-end load-balancer (either Apache or Nginx) to *NOT* proxy these headers. - */ + * If the user has authenticated through Shibboleth and is returning from the Identity Provider (IdP), + * there should be a `shib-session-id` header in the request. If the user is indicating that he + * wants to log in with Shibboleth, no such header will be present. + * + * It's up to the front-end load-balancer (either Apache or Nginx) to *NOT* proxy these headers. + */ const sessionId = req.headers['shib-session-id']; if (sessionId) { // Pass the request as the first argument to the verify function if @@ -70,20 +70,20 @@ Strategy.prototype.authenticate = function(req) { } /* - * The user is coming back from the IdP. mod_shib will pass all the user his attributes as headers. - * - * The following is a subset of the possible headers that the Shibboleth SP software proxies: - * - remote_user : Identifies the user with the application. Shibboleth needs to have been configured appropriately so that it knows on which attributes this value is based on - * - Shib-Application-ID : The applicationId property derived for the request. - * - Shib-Session-ID : The internal session key assigned to the session associated with the request. - * - Shib-Identity-Provider : The entityID of the IdP that authenticated the user associated with the request. - * - * Depending on what profile attributes the IdP releases, other headers might be in the request. - * The Shibboleth SP can be configured to map certain attributes to request headers. Ideally, no mapping should happen - * within OAE but in the shib software. See https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPAttributeAccess - * for more information. As the headers are exposed as a simple JSON object on the request, we can pass them straight - * into the verify function, which can then take care of getting/creating the OAE user object. - */ + * The user is coming back from the IdP. mod_shib will pass all the user his attributes as headers. + * + * The following is a subset of the possible headers that the Shibboleth SP software proxies: + * - remote_user : Identifies the user with the application. Shibboleth needs to have been configured appropriately so that it knows on which attributes this value is based on + * - Shib-Application-ID : The applicationId property derived for the request. + * - Shib-Session-ID : The internal session key assigned to the session associated with the request. + * - Shib-Identity-Provider : The entityID of the IdP that authenticated the user associated with the request. + * + * Depending on what profile attributes the IdP releases, other headers might be in the request. + * The Shibboleth SP can be configured to map certain attributes to request headers. Ideally, no mapping should happen + * within OAE but in the shib software. See https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPAttributeAccess + * for more information. As the headers are exposed as a simple JSON object on the request, we can pass them straight + * into the verify function, which can then take care of getting/creating the OAE user object. + */ verifyFn(req.headers, (err, user) => { if (err) { return self.error(new Error(err.msg)); @@ -94,17 +94,17 @@ Strategy.prototype.authenticate = function(req) { }); } else { /* - * There is no session yet. We redirect the user to Shibboleth. - * In case we know which IdP we want to use, we can append its entityID in the redirectUrl. - * For example, If we want to authentication with the Cambridge IdP - * /Shibboleth.sso/Login?target=/api/auth/shibboleth/sp/returned&entityId=https://shib.raven.cam.ac.uk/shibboleth - * - * The `target` parameter in the above example is where the IdP should send the user to - * once he is succesfully logged in. Note that this does NOT correspond with any URL in - * Hilary. That URL is protected by Apache and mod_shib and will eventually invoke a response - * at /api/auth/shibboleth/sp/callback. That callback URL should NOT be accessible via nginx - * as that could lead to user spoofing. - */ + * There is no session yet. We redirect the user to Shibboleth. + * In case we know which IdP we want to use, we can append its entityID in the redirectUrl. + * For example, If we want to authentication with the Cambridge IdP + * /Shibboleth.sso/Login?target=/api/auth/shibboleth/sp/returned&entityId=https://shib.raven.cam.ac.uk/shibboleth + * + * The `target` parameter in the above example is where the IdP should send the user to + * once he is succesfully logged in. Note that this does NOT correspond with any URL in + * Hilary. That URL is protected by Apache and mod_shib and will eventually invoke a response + * at /api/auth/shibboleth/sp/callback. That callback URL should NOT be accessible via nginx + * as that could lead to user spoofing. + */ let redirectUrl = util.format( '/Shibboleth.sso/Login?target=%s', encodeURIComponent('/api/auth/shibboleth/sp/returned') @@ -112,6 +112,7 @@ Strategy.prototype.authenticate = function(req) { if (self.options.idpEntityID) { redirectUrl += util.format('&entityID=%s', encodeURIComponent(self.options.idpEntityID)); } + return self.redirect(redirectUrl); } }; @@ -119,4 +120,4 @@ Strategy.prototype.authenticate = function(req) { /** * Expose `Strategy` */ -module.exports = Strategy; +export default Strategy; diff --git a/packages/oae-authentication/lib/strategies/signed/init.js b/packages/oae-authentication/lib/strategies/signed/init.js index 0ef00db43f..49a5c779e4 100644 --- a/packages/oae-authentication/lib/strategies/signed/init.js +++ b/packages/oae-authentication/lib/strategies/signed/init.js @@ -13,11 +13,11 @@ * permissions and limitations under the License. */ -const AuthenticationAPI = require('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const SignedStrategy = require('oae-authentication/lib/strategies/signed/strategy'); +import * as AuthenticationAPI from 'oae-authentication'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import SignedStrategy from 'oae-authentication/lib/strategies/signed/strategy'; -module.exports = function() { +export default function() { const strategy = {}; /** @@ -37,4 +37,4 @@ module.exports = function() { // Register our strategy. AuthenticationAPI.registerStrategy(AuthenticationConstants.providers.SIGNED, strategy); -}; +} diff --git a/packages/oae-authentication/lib/strategies/signed/rest.js b/packages/oae-authentication/lib/strategies/signed/rest.js index 578fb67ca6..b01141e5c4 100644 --- a/packages/oae-authentication/lib/strategies/signed/rest.js +++ b/packages/oae-authentication/lib/strategies/signed/rest.js @@ -13,14 +13,14 @@ * permissions and limitations under the License. */ -const passport = require('passport'); +import passport from 'passport'; -const OAE = require('oae-util/lib/oae'); -const OaeServer = require('oae-util/lib/server'); +import * as OAE from 'oae-util/lib/oae'; +import * as OaeServer from 'oae-util/lib/server'; -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationSignedUtil = require('oae-authentication/lib/strategies/signed/util'); -const AuthenticationUtil = require('oae-authentication/lib/util'); +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationSignedUtil from 'oae-authentication/lib/strategies/signed/util'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; // Ensure that the signed auth URL bypass CSRF validation. // It has its own authenticity handling. @@ -43,17 +43,13 @@ OaeServer.addSafePathPrefix('/api/auth/signed'); * @HttpResponse 404 There is no tenant with alias ... */ OAE.globalAdminRouter.on('get', '/api/auth/signed/tenant', (req, res) => { - AuthenticationSignedUtil.getSignedTenantAuthenticationRequest( - req.ctx, - req.query.tenant, - (err, requestInfo) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(200).send(requestInfo); + AuthenticationSignedUtil.getSignedTenantAuthenticationRequest(req.ctx, req.query.tenant, (err, requestInfo) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + return res.status(200).send(requestInfo); + }); }); /*! @@ -113,11 +109,10 @@ OAE.tenantRouter.on('get', '/api/auth/signed/become', _getBecomeUserAuthenticati */ OAE.tenantRouter.on('post', '/api/auth/signed', (req, res, next) => { // Get the ID under which we registered this strategy for this tenant - const strategyId = AuthenticationUtil.getStrategyId( - req.tenant, - AuthenticationConstants.providers.SIGNED - ); + const strategyId = AuthenticationUtil.getStrategyId(req.tenant, AuthenticationConstants.providers.SIGNED); // Authenticate this request using the information passport.authenticate(strategyId, { successRedirect: '/', failureRedirect: '/' })(req, res, next); }); + +export default OAE; diff --git a/packages/oae-authentication/lib/strategies/signed/strategy.js b/packages/oae-authentication/lib/strategies/signed/strategy.js index 2df5fef7d0..97b3364783 100644 --- a/packages/oae-authentication/lib/strategies/signed/strategy.js +++ b/packages/oae-authentication/lib/strategies/signed/strategy.js @@ -13,14 +13,14 @@ * permissions and limitations under the License. */ -const util = require('util'); -const passport = require('passport'); +import util from 'util'; +import passport from 'passport'; -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationSignedUtil = require('oae-authentication/lib/strategies/signed/util'); -const AuthenticationUtil = require('oae-authentication/lib/util'); +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationSignedUtil from 'oae-authentication/lib/strategies/signed/util'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; const Strategy = function() { passport.Strategy.call(this); @@ -42,70 +42,64 @@ Strategy.prototype.authenticate = function(req) { const self = this; // Verify and extract the signed body from the request - AuthenticationSignedUtil.verifySignedAuthenticationBody( - req.ctx, - req.body, - (err, userId, becomeUserId) => { - if (err) { - return self.fail(err.msg, err.code); + AuthenticationSignedUtil.verifySignedAuthenticationBody(req.ctx, req.body, (err, userId, becomeUserId) => { + if (err) { + return self.fail(err.msg, err.code); + } + + // Will hold the user and imposter (if any) for the authentication callback + let authObj = null; + let strategyId = null; + + // This is a valid request, get the user and pass it on + PrincipalsDAO.getPrincipal(userId, (err, user) => { + if (err && err.code !== 404) { + // Ensure there wasn't un unexpected error fetching the user + return self.error(new Error(err.msg)); } - // Will hold the user and imposter (if any) for the authentication callback - let authObj = null; - let strategyId = null; + if (err && err.code === 404) { + // Ensure the authenticating user exists + return self.fail(err.msg, 404); + } - // This is a valid request, get the user and pass it on - PrincipalsDAO.getPrincipal(userId, (err, user) => { + if (!becomeUserId) { + // If the user is not trying to impersonate someone else, we can + // simply authenticate normally as this user + strategyId = AuthenticationUtil.getStrategyId(user.tenant, AuthenticationConstants.providers.SIGNED); + authObj = { user, strategyId }; + AuthenticationUtil.logAuthenticationSuccess(req, authObj, self.name); + return self.success(authObj); + } + + // If we get here we are trying to become someone, fetch that person + // and perform the appropriate permission checks + PrincipalsDAO.getPrincipal(becomeUserId, (err, becomeUser) => { if (err && err.code !== 404) { - // Ensure there wasn't un unexpected error fetching the user + // Ensure there wasn't un unexpected error fetching the target user return self.error(new Error(err.msg)); } + if (err && err.code === 404) { - // Ensure the authenticating user exists + // Ensure the impersonated user exists return self.fail(err.msg, 404); } - if (!becomeUserId) { - // If the user is not trying to impersonate someone else, we can - // simply authenticate normally as this user - strategyId = AuthenticationUtil.getStrategyId( - user.tenant, - AuthenticationConstants.providers.SIGNED - ); - authObj = { user, strategyId }; - AuthenticationUtil.logAuthenticationSuccess(req, authObj, self.name); - return self.success(authObj); + + if (!user.isAdmin(becomeUser.tenant.alias)) { + // Ensure the authenticated user (impersonator) has the required access to become this user + return self.fail('You are not authorized to become the target user', 401); } - // If we get here we are trying to become someone, fetch that person - // and perform the appropriate permission checks - PrincipalsDAO.getPrincipal(becomeUserId, (err, becomeUser) => { - if (err && err.code !== 404) { - // Ensure there wasn't un unexpected error fetching the target user - return self.error(new Error(err.msg)); - } - if (err && err.code === 404) { - // Ensure the impersonated user exists - return self.fail(err.msg, 404); - } - if (!user.isAdmin(becomeUser.tenant.alias)) { - // Ensure the authenticated user (impersonator) has the required access to become this user - return self.fail('You are not authorized to become the target user', 401); - } - - strategyId = AuthenticationUtil.getStrategyId( - user.tenant, - AuthenticationConstants.providers.SIGNED - ); - authObj = { user: becomeUser, imposter: user, strategyId }; - AuthenticationUtil.logAuthenticationSuccess(req, authObj, self.name); - return self.success(authObj); - }); + strategyId = AuthenticationUtil.getStrategyId(user.tenant, AuthenticationConstants.providers.SIGNED); + authObj = { user: becomeUser, imposter: user, strategyId }; + AuthenticationUtil.logAuthenticationSuccess(req, authObj, self.name); + return self.success(authObj); }); - } - ); + }); + }); }; /** * Expose `Strategy` */ -module.exports = Strategy; +export default Strategy; diff --git a/packages/oae-authentication/lib/strategies/signed/util.js b/packages/oae-authentication/lib/strategies/signed/util.js index b5443f4c64..0a44719831 100644 --- a/packages/oae-authentication/lib/strategies/signed/util.js +++ b/packages/oae-authentication/lib/strategies/signed/util.js @@ -13,14 +13,14 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const Signature = require('oae-util/lib/signature'); -const TenantsAPI = require('oae-tenants'); -const TenantsUtil = require('oae-tenants/lib/util'); -const { Validator } = require('oae-authz/lib/validator'); +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as Signature from 'oae-util/lib/signature'; +import * as TenantsAPI from 'oae-tenants'; +import * as TenantsUtil from 'oae-tenants/lib/util'; +import { Validator } from 'oae-authz/lib/validator'; const TIME_1_MINUTE_IN_SECONDS = 60; @@ -47,11 +47,11 @@ const getSignedTenantAuthenticationRequest = function(ctx, tenantAlias, callback if (validator.hasErrors()) { return callback(validator.getFirstError()); } + if (ctx.imposter()) { return callback({ code: 401, - msg: - 'You cannot create a signed authentication token to a tenant while impostering another user' + msg: 'You cannot create a signed authentication token to a tenant while impostering another user' }); } @@ -64,11 +64,7 @@ const getSignedTenantAuthenticationRequest = function(ctx, tenantAlias, callback } const data = { tenantAlias, userId: ctx.user().id }; - const signedData = Signature.createExpiringSignature( - data, - TIME_1_MINUTE_IN_SECONDS, - TIME_1_MINUTE_IN_SECONDS - ); + const signedData = Signature.createExpiringSignature(data, TIME_1_MINUTE_IN_SECONDS, TIME_1_MINUTE_IN_SECONDS); // Include the authenticating `userId` in the signed data. It isn't necessary to include the tenant alias in // the body, as we can assume that from the target context during the verification phase, so we omit it from @@ -106,12 +102,14 @@ const getSignedBecomeUserAuthenticationRequest = function(ctx, becomeUserId, cal if (validator.hasErrors()) { return callback(validator.getFirstError()); } + if (!ctx.user().isAdmin(ctx.user().tenant.alias)) { // Only users who have an admin status can become someone. This check is redundant to // the check that verifies the current user is an admin of the target user's tenant, // however this can be done before we go to the database return callback({ code: 401, msg: 'Only administrators can become a user' }); } + if (ctx.imposter()) { // If the session is already impostering someone, they cannot imposter someone else. // For example: Global admin imposters a tenant administrator, then further imposters @@ -125,10 +123,12 @@ const getSignedBecomeUserAuthenticationRequest = function(ctx, becomeUserId, cal if (err) { return callback(err); } + if (!ctx.user().isAdmin(becomeUser.tenant.alias)) { // The current user must be an admin of the target user's tenant in order to become them return callback({ code: 401, msg: 'You are not authorized to become this user' }); } + if (becomeUser.isAdmin(becomeUser.tenant.alias) && !ctx.user().isGlobalAdmin()) { // If the target user is a tenant admin, only the global admin can become them return callback({ @@ -144,11 +144,7 @@ const getSignedBecomeUserAuthenticationRequest = function(ctx, becomeUserId, cal userId: ctx.user().id, becomeUserId }; - const signedData = Signature.createExpiringSignature( - data, - TIME_1_MINUTE_IN_SECONDS, - TIME_1_MINUTE_IN_SECONDS - ); + const signedData = Signature.createExpiringSignature(data, TIME_1_MINUTE_IN_SECONDS, TIME_1_MINUTE_IN_SECONDS); // Include the authenticating `userId` and target `becomeUserId` in the signed data. It isn't necessary to // include the tenant alias in the body, as we can assume that from the target context during the verification @@ -172,9 +168,7 @@ const getSignedBecomeUserAuthenticationRequest = function(ctx, becomeUserId, cal */ const verifySignedAuthenticationBody = function(ctx, body, callback) { const validator = new Validator(); - validator - .check(body.userId, { code: 400, msg: 'Invalid user id provided as the authenticating user' }) - .isUserId(); + validator.check(body.userId, { code: 400, msg: 'Invalid user id provided as the authenticating user' }).isUserId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -208,7 +202,7 @@ const _getSignedAuthenticationUrl = function(tenant) { return util.format('%s/api/auth/signed', TenantsUtil.getBaseUrl(tenant)); }; -module.exports = { +export { getSignedTenantAuthenticationRequest, getSignedBecomeUserAuthenticationRequest, verifySignedAuthenticationBody diff --git a/packages/oae-authentication/lib/strategies/twitter/init.js b/packages/oae-authentication/lib/strategies/twitter/init.js index 9f077d42ab..cfcfe8f289 100644 --- a/packages/oae-authentication/lib/strategies/twitter/init.js +++ b/packages/oae-authentication/lib/strategies/twitter/init.js @@ -13,29 +13,29 @@ * permissions and limitations under the License. */ -const TwitterStrategy = require('passport-twitter').Strategy; +import twitterPassport from 'passport-twitter'; -const ConfigAPI = require('oae-config'); -const log = require('oae-logger').logger('oae-authentication'); +import * as ConfigAPI from 'oae-config'; +import { logger } from 'oae-logger'; -const AuthenticationAPI = require('oae-authentication'); +import * as AuthenticationAPI from 'oae-authentication'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; -const AuthenticationConfig = ConfigAPI.config('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationUtil = require('oae-authentication/lib/util'); +const TwitterStrategy = twitterPassport.Strategy; -module.exports = function() { +const log = logger('oae-authentication'); + +const AuthenticationConfig = ConfigAPI.setUpConfig('oae-authentication'); + +export default function() { const strategy = {}; /** * @see oae-authentication/lib/strategy#shouldBeEnabled */ strategy.shouldBeEnabled = function(tenantAlias) { - return AuthenticationConfig.getValue( - tenantAlias, - AuthenticationConstants.providers.TWITTER, - 'enabled' - ); + return AuthenticationConfig.getValue(tenantAlias, AuthenticationConstants.providers.TWITTER, 'enabled'); }; /** @@ -43,11 +43,7 @@ module.exports = function() { */ strategy.getPassportStrategy = function(tenant) { // We fetch the config values *in* the getPassportStrategy so it can be re-configured at run-time. - const consumerKey = AuthenticationConfig.getValue( - tenant.alias, - AuthenticationConstants.providers.TWITTER, - 'key' - ); + const consumerKey = AuthenticationConfig.getValue(tenant.alias, AuthenticationConstants.providers.TWITTER, 'key'); const consumerSecret = AuthenticationConfig.getValue( tenant.alias, AuthenticationConstants.providers.TWITTER, @@ -58,10 +54,7 @@ module.exports = function() { { consumerKey, consumerSecret, - callbackURL: AuthenticationUtil.constructCallbackUrl( - tenant, - AuthenticationConstants.providers.TWITTER - ), + callbackURL: AuthenticationUtil.constructCallbackUrl(tenant, AuthenticationConstants.providers.TWITTER), passReqToCallback: true }, (req, token, tokenSecret, profile, done) => { @@ -103,4 +96,4 @@ module.exports = function() { // Register our strategy. AuthenticationAPI.registerStrategy(AuthenticationConstants.providers.TWITTER, strategy); -}; +} diff --git a/packages/oae-authentication/lib/strategies/twitter/rest.js b/packages/oae-authentication/lib/strategies/twitter/rest.js index 89634bd04d..fc22b729ac 100644 --- a/packages/oae-authentication/lib/strategies/twitter/rest.js +++ b/packages/oae-authentication/lib/strategies/twitter/rest.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -const OAE = require('oae-util/lib/oae'); +import * as OAE from 'oae-util/lib/oae'; -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationUtil = require('oae-authentication/lib/util'); +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationUtil from 'oae-authentication/lib/util'; /** * @REST postAuthTwitter @@ -32,10 +32,7 @@ const AuthenticationUtil = require('oae-authentication/lib/util'); */ OAE.tenantRouter.on('post', '/api/auth/twitter', (req, res, next) => { // Get the ID under which we registered this strategy for this tenant - const strategyId = AuthenticationUtil.getStrategyId( - req.tenant, - AuthenticationConstants.providers.TWITTER - ); + const strategyId = AuthenticationUtil.getStrategyId(req.tenant, AuthenticationConstants.providers.TWITTER); // Perform the initial authentication step AuthenticationUtil.handleExternalSetup(strategyId, null, req, res, next); @@ -54,11 +51,10 @@ OAE.tenantRouter.on('post', '/api/auth/twitter', (req, res, next) => { */ OAE.tenantRouter.on('get', '/api/auth/twitter/callback', (req, res, next) => { // Get the ID under which we registered this strategy for this tenant - const strategyId = AuthenticationUtil.getStrategyId( - req.tenant, - AuthenticationConstants.providers.TWITTER - ); + const strategyId = AuthenticationUtil.getStrategyId(req.tenant, AuthenticationConstants.providers.TWITTER); // Log the user in AuthenticationUtil.handleExternalCallback(strategyId, req, res, next); }); + +export default OAE; diff --git a/packages/oae-authentication/lib/strategy.js b/packages/oae-authentication/lib/strategy.js index 843ac51f39..868c692d35 100644 --- a/packages/oae-authentication/lib/strategy.js +++ b/packages/oae-authentication/lib/strategy.js @@ -31,7 +31,4 @@ const shouldBeEnabled = function(tenantAlias) {}; // eslint-disable-next-line no-unused-vars const getPassportStrategy = function(tenant) {}; -module.exports = { - shouldBeEnabled, - getPassportStrategy -}; +export { shouldBeEnabled, getPassportStrategy }; diff --git a/packages/oae-authentication/lib/test/util.js b/packages/oae-authentication/lib/test/util.js index 128dd3ce97..0793fd2f6c 100644 --- a/packages/oae-authentication/lib/test/util.js +++ b/packages/oae-authentication/lib/test/util.js @@ -13,17 +13,18 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const util = require('util'); -const _ = require('underscore'); +import assert from 'assert'; +import util from 'util'; +import _ from 'underscore'; -const ConfigTestUtil = require('oae-config/lib/test/util'); -const { Cookie } = require('tough-cookie'); -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests/lib/util'); +import nock from 'nock'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import { Cookie } from 'tough-cookie'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests/lib/util'; -const AuthenticationAPI = require('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); +import * as AuthenticationAPI from 'oae-authentication'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; /** * Assert that the authentication config can be updated and the authentication strategies get refreshed @@ -225,10 +226,6 @@ const assertFacebookLoginFails = function(tenantHost, email, reason, callback) { * @api private */ const _nock = function() { - // Require nock in-line as it messes with the HTTP stack. We only want it - // required when the container is actively running the unit tests - const nock = require('nock'); - // Ensure we can still perform regular HTTP requests nock.enableNetConnect(); @@ -298,8 +295,7 @@ const _mockGoogleResponse = function(email) { }, url: 'https://plus.google.com/' + _.random(10000000), image: { - url: - 'https://lh5.googleusercontent.com/-wfVubfsOBV0/AAAAAAAAAAI/AAAAAAAAAGQ/rEb5FmsQuiA/photo.jpg?sz=50', + url: 'https://lh5.googleusercontent.com/-wfVubfsOBV0/AAAAAAAAAAI/AAAAAAAAAGQ/rEb5FmsQuiA/photo.jpg?sz=50', isDefault: false }, isPlusUser: true, @@ -309,12 +305,13 @@ const _mockGoogleResponse = function(email) { if (email) { mockedResponse.emails.push({ value: email, type: 'account' }); } + nock('https://www.googleapis.com') .get('/plus/v1/people/me?access_token=' + accessToken) .reply(200, mockedResponse); }; -module.exports = { +export { assertUpdateAuthConfigSucceeds, assertLocalLoginSucceeds, assertGoogleLoginSucceeds, diff --git a/packages/oae-authentication/lib/util.js b/packages/oae-authentication/lib/util.js index 641054d67f..b041e4d0b8 100644 --- a/packages/oae-authentication/lib/util.js +++ b/packages/oae-authentication/lib/util.js @@ -13,18 +13,20 @@ * permissions and limitations under the License. */ -const crypto = require('crypto'); -const url = require('url'); -const util = require('util'); -const _ = require('underscore'); -const cookieParser = require('cookie-parser'); -const cookieSession = require('cookie-session'); -const MobileDetect = require('mobile-detect'); -const passport = require('passport'); - -const { Context } = require('oae-context'); -const log = require('oae-logger').logger('oae-authentication'); -const TenantsUtil = require('oae-tenants/lib/util'); +import crypto from 'crypto'; +import url from 'url'; +import util from 'util'; +import _ from 'underscore'; +import cookieParser from 'cookie-parser'; +import cookieSession from 'cookie-session'; +import MobileDetect from 'mobile-detect'; +import passport from 'passport'; +import { Context } from 'oae-context'; +import { logger } from 'oae-logger'; +import * as TenantsUtil from 'oae-tenants/lib/util'; +import { getOrCreateUser } from 'oae-authentication'; + +const log = logger('oae-authentication'); /** * Setup the necessary authentication middleware @@ -159,12 +161,7 @@ const logAuthenticationSuccess = function(req, authInfo, strategyName) { log().info( data, - util.format( - 'Login for "%s" to tenant "%s" from "%s"', - user.id, - tenantAlias, - req.headers['x-forwarded-for'] - ) + util.format('Login for "%s" to tenant "%s" from "%s"', user.id, tenantAlias, req.headers['x-forwarded-for']) ); }; @@ -186,12 +183,14 @@ const handlePassportError = function(req, res, next) { // If someone tried to sign in with a disabled strategy } + if (err.message && err.message.indexOf('Unknown authentication strategy') === 0) { log().warn({ host: req.hostname }, 'Authentication attempt with disabled strategy'); return res.redirect('/?authentication=disabled'); // Generic error } + log().error({ err, host: req.hostname }, 'An error occurred during login'); return res.redirect('/?authentication=error'); } @@ -284,15 +283,7 @@ const handleExternalGetOrCreateUser = function( // Require the AuthenticationAPI inline to avoid cross-dependency issues // during initialization const ctx = new Context(req.tenant); - return require('oae-authentication').getOrCreateUser( - ctx, - authProvider, - externalId, - providerProperties, - displayName, - opts, - callback - ); + return getOrCreateUser(ctx, authProvider, externalId, providerProperties, displayName, opts, callback); }; /** @@ -315,15 +306,13 @@ const handleExternalCallback = function(strategyId, req, res, next) { if (err) { return errorHandler(err); } + if (!user) { // The user's credentials didn't check out. This would rarely occur in a // normal situation as external auth providers don't usually redirect with // bad parameters in the request, so somebody is probably tampering with it. // We bail out immediately - log().warn( - { challenges, status }, - 'Possible tampering of external callback request detected' - ); + log().warn({ challenges, status }, 'Possible tampering of external callback request detected'); return res.redirect('/?authentication=failed&reason=tampering'); } @@ -430,6 +419,7 @@ const renderTemplate = function(template, data) { if (data[variableName]) { return data[variableName]; } + return ''; }); return result; @@ -450,7 +440,7 @@ const _getRequestInvitationInfo = function(req) { return _.pick(parsedRedirectUrl.query, 'invitationToken', 'invitationEmail'); }; -module.exports = { +export { setupAuthMiddleware, hashAndComparePassword, hashPassword, diff --git a/packages/oae-authentication/tests/test-auth-local.js b/packages/oae-authentication/tests/test-auth-local.js index 80677838f4..ce192b6672 100644 --- a/packages/oae-authentication/tests/test-auth-local.js +++ b/packages/oae-authentication/tests/test-auth-local.js @@ -13,20 +13,20 @@ * permissions and limitations under the License. */ -const assert = require('assert'); +import assert from 'assert'; -const Cassandra = require('oae-util/lib/cassandra'); -const ConfigTestUtil = require('oae-config/lib/test/util'); -const { Context } = require('oae-context'); -const PrincipalsAPI = require('oae-principals'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const TestsUtil = require('oae-tests'); +import Cassandra from 'oae-util/lib/cassandra'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import { Context } from 'oae-context'; +import PrincipalsAPI from 'oae-principals'; +import RestAPI from 'oae-rest'; +import { RestContext } from 'oae-rest/lib/model'; +import * as TestsUtil from 'oae-tests'; -const AuthenticationAPI = require('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationTestUtil = require('oae-authentication/lib/test/util'); -const { LoginId } = require('oae-authentication/lib/model'); +import * as AuthenticationAPI from 'oae-authentication'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationTestUtil from 'oae-authentication/lib/test/util'; +import { LoginId } from 'oae-authentication/lib/model'; describe('Authentication', () => { // Rest context that can be used for anonymous requests on the cambridge tenant @@ -130,8 +130,8 @@ describe('Authentication', () => { let count = 0; /*! - * Callback for the create user requests - */ + * Callback for the create user requests + */ const checkComplete = function() { count++; // Make sure both calls to createUser have returned diff --git a/packages/oae-authentication/tests/test-auth.js b/packages/oae-authentication/tests/test-auth.js index c854197629..7d226ee280 100644 --- a/packages/oae-authentication/tests/test-auth.js +++ b/packages/oae-authentication/tests/test-auth.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -const assert = require('assert'); +import assert from 'assert'; -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); +import RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; describe('Authentication', () => { // Rest context that can be used for anonymous requests on the cambridge tenant @@ -50,10 +50,7 @@ describe('Authentication', () => { it("verify that only authorized admins can request a user's login ids", callback => { // Generate a user id const username = TestsUtil.generateTestUserId(); - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); // Verify that an error is thrown when a malformed id was specified RestAPI.Authentication.getUserLoginIds(globalAdminRestContext, 'not an id', (err, loginIds) => { @@ -61,89 +58,65 @@ describe('Authentication', () => { assert.strictEqual(err.code, 400); // Verify that an error is thrown when a non-user id is specified - RestAPI.Authentication.getUserLoginIds( - globalAdminRestContext, - 'g:not:a-user-id', - (err, loginIds) => { + RestAPI.Authentication.getUserLoginIds(globalAdminRestContext, 'g:not:a-user-id', (err, loginIds) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + + // Verify that an error is thrown when an unexisting user id was specified + RestAPI.Authentication.getUserLoginIds(globalAdminRestContext, 'u:camtest:abcdefghij', (err, loginIds) => { assert.ok(err); - assert.strictEqual(err.code, 400); - - // Verify that an error is thrown when an unexisting user id was specified - RestAPI.Authentication.getUserLoginIds( - globalAdminRestContext, - 'u:camtest:abcdefghij', - (err, loginIds) => { - assert.ok(err); - assert.strictEqual(err.code, 404); - - // Create a test user - RestAPI.User.createUser( - camAdminRestContext, - username, - 'password', - 'Test User', - email, - null, - (err, createdUser) => { - assert.ok(!err); - - // Verify that an error is thrown when an anonymous user on the global admin router requests the login ids for the test user - RestAPI.Authentication.getUserLoginIds( - anonymousGlobalRestContext, - createdUser.id, - (err, loginIds) => { - assert.ok(err); - assert.strictEqual(err.code, 401); - - // Verify that an error is thrown when an anonymous user on the tenant router requests the login ids for the test user + assert.strictEqual(err.code, 404); + + // Create a test user + RestAPI.User.createUser( + camAdminRestContext, + username, + 'password', + 'Test User', + email, + null, + (err, createdUser) => { + assert.ok(!err); + + // Verify that an error is thrown when an anonymous user on the global admin router requests the login ids for the test user + RestAPI.Authentication.getUserLoginIds(anonymousGlobalRestContext, createdUser.id, (err, loginIds) => { + assert.ok(err); + assert.strictEqual(err.code, 401); + + // Verify that an error is thrown when an anonymous user on the tenant router requests the login ids for the test user + RestAPI.Authentication.getUserLoginIds(anonymousCamRestContext, createdUser.id, (err, loginIds) => { + assert.ok(err); + assert.strictEqual(err.code, 401); + + // Verify that an error is thrown when an unauthorized tenant admin requests the login ids for the test user + RestAPI.Authentication.getUserLoginIds(gtAdminRestContext, createdUser.id, (err, loginIds) => { + assert.ok(err); + assert.strictEqual(err.code, 401); + + // Verify that a tenant admin can request the login ids for the test user + RestAPI.Authentication.getUserLoginIds(camAdminRestContext, createdUser.id, (err, loginIds) => { + assert.ok(!err); + assert.ok(loginIds); + + // Verify that a global admin can request the login ids for the test user RestAPI.Authentication.getUserLoginIds( - anonymousCamRestContext, + globalAdminRestContext, createdUser.id, (err, loginIds) => { - assert.ok(err); - assert.strictEqual(err.code, 401); - - // Verify that an error is thrown when an unauthorized tenant admin requests the login ids for the test user - RestAPI.Authentication.getUserLoginIds( - gtAdminRestContext, - createdUser.id, - (err, loginIds) => { - assert.ok(err); - assert.strictEqual(err.code, 401); - - // Verify that a tenant admin can request the login ids for the test user - RestAPI.Authentication.getUserLoginIds( - camAdminRestContext, - createdUser.id, - (err, loginIds) => { - assert.ok(!err); - assert.ok(loginIds); - - // Verify that a global admin can request the login ids for the test user - RestAPI.Authentication.getUserLoginIds( - globalAdminRestContext, - createdUser.id, - (err, loginIds) => { - assert.ok(!err); - assert.ok(loginIds); - - return callback(); - } - ); - } - ); - } - ); + assert.ok(!err); + assert.ok(loginIds); + + return callback(); } ); - } - ); - } - ); + }); + }); + }); + }); } ); - } - ); + }); + }); }); }); @@ -153,36 +126,21 @@ describe('Authentication', () => { it("verify that a user's login ids can be successfully returned", callback => { // Generate a user id const username = TestsUtil.generateTestUserId(); - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); // Create a test user - RestAPI.User.createUser( - camAdminRestContext, - username, - 'password', - 'Test User', - email, - null, - (err, createdUser) => { + RestAPI.User.createUser(camAdminRestContext, username, 'password', 'Test User', email, null, (err, createdUser) => { + assert.ok(!err); + + // Verify that a global admin can request the login ids for the test user + RestAPI.Authentication.getUserLoginIds(globalAdminRestContext, createdUser.id, (err, loginIds) => { assert.ok(!err); + assert.ok(loginIds); + assert.ok(loginIds.local); + assert.strictEqual(loginIds.local, username.toLowerCase()); - // Verify that a global admin can request the login ids for the test user - RestAPI.Authentication.getUserLoginIds( - globalAdminRestContext, - createdUser.id, - (err, loginIds) => { - assert.ok(!err); - assert.ok(loginIds); - assert.ok(loginIds.local); - assert.strictEqual(loginIds.local, username.toLowerCase()); - - return callback(); - } - ); - } - ); + return callback(); + }); + }); }); }); diff --git a/packages/oae-authentication/tests/test-cookies.js b/packages/oae-authentication/tests/test-cookies.js index 7a5b6bfd12..c3675ea3be 100644 --- a/packages/oae-authentication/tests/test-cookies.js +++ b/packages/oae-authentication/tests/test-cookies.js @@ -13,13 +13,11 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); - -const AuthenticationAPI = require('oae-authentication'); +import RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; describe('Authentication', () => { // Rest context that can be used every time we need to make a request as a tenant admin @@ -40,16 +38,16 @@ describe('Authentication', () => { describe('Local authentication', () => { /*! - * Given a set of user agents, perform an authentication using each and record the cookie - * data that is returned in the authentication response for each user agent - * - * @param {RestContext} restContext The REST context to use for each authentication - * @param {String} username The username to use for each authentication - * @param {String} password The password to use for each authentication - * @param {String[]} userAgents A list of user agents to authenticate with - * @param {Function} callback Invoked when all authentications have successfully completed - * @param {Object} callback.userAgentCookies An object whose keys are the user agents, and values are an array of `request` Cookie's that were returned in the authentication response - */ + * Given a set of user agents, perform an authentication using each and record the cookie + * data that is returned in the authentication response for each user agent + * + * @param {RestContext} restContext The REST context to use for each authentication + * @param {String} username The username to use for each authentication + * @param {String} password The password to use for each authentication + * @param {String[]} userAgents A list of user agents to authenticate with + * @param {Function} callback Invoked when all authentications have successfully completed + * @param {Object} callback.userAgentCookies An object whose keys are the user agents, and values are an array of `request` Cookie's that were returned in the authentication response + */ const _getCookiesForUserAgents = function( restContext, username, @@ -82,9 +80,7 @@ describe('Authentication', () => { assert.ok(!err); // Aggregate the cookies into the user agent map - _userAgentCookies[userAgent] = restContext.cookieJar._jar.getCookiesSync( - restContext.host - ); + _userAgentCookies[userAgent] = restContext.cookieJar._jar.getCookiesSync(restContext.host); return _getCookiesForUserAgents( restContext, @@ -105,8 +101,8 @@ describe('Authentication', () => { */ it('verify cookie expiration for mobile and non-mobile browsers', callback => { /*! - * A collection of user agents for a variety of desktop / non-mobile clients - */ + * A collection of user agents for a variety of desktop / non-mobile clients + */ const nonMobileUserAgents = [ // Firefox variants 'Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0', @@ -141,8 +137,8 @@ describe('Authentication', () => { ]; /*! - * A collection of user agents for mobile devices (phones, tablets, etc...) - */ + * A collection of user agents for mobile devices (phones, tablets, etc...) + */ const mobileUserAgents = [ // IPhone/iPad variants 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7', @@ -171,93 +167,76 @@ describe('Authentication', () => { // Create a test user const username = TestsUtil.generateTestUserId(); - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); - RestAPI.User.createUser( - camAdminRestContext, - username, - 'password', - 'Test User', - email, - {}, - (err, createdUser) => { - assert.ok(!err); - const userRestContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.cam.host, - username, - 'password' - ); - - // Get all user agents for a user. When using a user tenant, mobile user-agents - // should result in a cookie that has a length expiry + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); + RestAPI.User.createUser(camAdminRestContext, username, 'password', 'Test User', email, {}, (err, createdUser) => { + assert.ok(!err); + const userRestContext = TestsUtil.createTenantRestContext( + global.oaeTests.tenants.cam.host, + username, + 'password' + ); + + // Get all user agents for a user. When using a user tenant, mobile user-agents + // should result in a cookie that has a length expiry + _getCookiesForUserAgents(userRestContext, username, 'password', allUserAgents, userAgentCookies => { + assert.strictEqual(_.keys(userAgentCookies).length, allUserAgents.length); + + // Ensure each mobile user agent has a cookie with an explicit expiry time that + // is more than 29 days into the future + _.each(mobileUserAgents, mobileUserAgent => { + const cookies = userAgentCookies[mobileUserAgent]; + + assert.strictEqual(cookies.length, 2); + _.each(cookies, cookie => { + // eslint-disable-next-line new-cap + assert.ok(_.isNumber(cookie.TTL())); + // eslint-disable-next-line new-cap + assert.ok(cookie.TTL() > 1000 * 60 * 60 * 24 * 29); + // eslint-disable-next-line new-cap + assert.notStrictEqual(cookie.TTL(), Infinity); + }); + }); + + // Ensure each non-mobile user agent has a cookie without an explicit expiry + // (i.e., browser session cookie) + _.each(nonMobileUserAgents, nonMobileUserAgent => { + const cookies = userAgentCookies[nonMobileUserAgent]; + + assert.strictEqual(cookies.length, 2); + _.each(cookies, cookie => { + // eslint-disable-next-line new-cap + assert.strictEqual(cookie.TTL(), Infinity); + }); + }); + + // Get all user agents for a global admin login. When using the global admin + // tenant, both mobile and non-mobile user-agents should not have an extended + // expiry _getCookiesForUserAgents( - userRestContext, - username, - 'password', + globalAdminRestContext, + 'administrator', + 'administrator', allUserAgents, userAgentCookies => { assert.strictEqual(_.keys(userAgentCookies).length, allUserAgents.length); - // Ensure each mobile user agent has a cookie with an explicit expiry time that - // is more than 29 days into the future - _.each(mobileUserAgents, mobileUserAgent => { - const cookies = userAgentCookies[mobileUserAgent]; - - assert.strictEqual(cookies.length, 2); - _.each(cookies, cookie => { - // eslint-disable-next-line new-cap - assert.ok(_.isNumber(cookie.TTL())); - // eslint-disable-next-line new-cap - assert.ok(cookie.TTL() > 1000 * 60 * 60 * 24 * 29); - // eslint-disable-next-line new-cap - assert.notStrictEqual(cookie.TTL(), Infinity); - }); - }); - - // Ensure each non-mobile user agent has a cookie without an explicit expiry - // (i.e., browser session cookie) - _.each(nonMobileUserAgents, nonMobileUserAgent => { - const cookies = userAgentCookies[nonMobileUserAgent]; + // Ensure all user agents have a cookie without an explicit expiry (i.e., + // browser session cookie) + _.each(allUserAgents, userAgent => { + const cookies = userAgentCookies[userAgent]; - assert.strictEqual(cookies.length, 2); + assert.ok(!_.isEmpty(cookies)); _.each(cookies, cookie => { // eslint-disable-next-line new-cap assert.strictEqual(cookie.TTL(), Infinity); }); }); - // Get all user agents for a global admin login. When using the global admin - // tenant, both mobile and non-mobile user-agents should not have an extended - // expiry - _getCookiesForUserAgents( - globalAdminRestContext, - 'administrator', - 'administrator', - allUserAgents, - userAgentCookies => { - assert.strictEqual(_.keys(userAgentCookies).length, allUserAgents.length); - - // Ensure all user agents have a cookie without an explicit expiry (i.e., - // browser session cookie) - _.each(allUserAgents, userAgent => { - const cookies = userAgentCookies[userAgent]; - - assert.ok(!_.isEmpty(cookies)); - _.each(cookies, cookie => { - // eslint-disable-next-line new-cap - assert.strictEqual(cookie.TTL(), Infinity); - }); - }); - - return callback(); - } - ); + return callback(); } ); - } - ); + }); + }); }); }); }); diff --git a/packages/oae-authentication/tests/test-external-strategies.js b/packages/oae-authentication/tests/test-external-strategies.js index 7b55ec4ace..f63cfdf392 100644 --- a/packages/oae-authentication/tests/test-external-strategies.js +++ b/packages/oae-authentication/tests/test-external-strategies.js @@ -13,28 +13,28 @@ * permissions and limitations under the License. */ -const path = require('path'); -const assert = require('assert'); -const fs = require('fs'); -const url = require('url'); -const util = require('util'); -const request = require('request'); -const _ = require('underscore'); -const xml2js = require('xml2js'); - -const ConfigTestUtil = require('oae-config/lib/test/util'); -const { Cookie } = require('tough-cookie'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); -const RestAPI = require('oae-rest'); -const RestUtil = require('oae-rest/lib/util'); -const TenantsTestUtil = require('oae-tenants/lib/test/util'); -const TestsUtil = require('oae-tests'); - -const AuthenticationAPI = require('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const AuthenticationTestUtil = require('oae-authentication/lib/test/util'); -const Config = require('oae-config/lib/api').config('oae-authentication'); -const ShibbolethAPI = require('oae-authentication/lib/strategies/shibboleth/api'); +import path from 'path'; +import assert from 'assert'; +import fs from 'fs'; +import url from 'url'; +import util from 'util'; +import _ from 'underscore'; +import request from 'request'; + +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import { Cookie } from 'tough-cookie'; +import PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import RestAPI from 'oae-rest'; +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; +import * as TestsUtil from 'oae-tests'; + +import * as AuthenticationAPI from 'oae-authentication'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as AuthenticationTestUtil from 'oae-authentication/lib/test/util'; +import { setUpConfig } from 'oae-config/lib/api'; +import * as ShibbolethAPI from 'oae-authentication/lib/strategies/shibboleth/api'; + +const Config = setUpConfig('oae-authentication'); describe('Authentication', () => { // Rest context that can be used every time we need to make a request as a global admin @@ -64,11 +64,7 @@ describe('Authentication', () => { * @param {Function} resetCallback Standard function that will be invoked when the strategy has been reset */ const _enableStrategy = function(strategyName, enabledCallback, resetCallback) { - const strategyStatus = Config.getValue( - global.oaeTests.tenants.global.alias, - strategyName, - 'enabled' - ); + const strategyStatus = Config.getValue(global.oaeTests.tenants.global.alias, strategyName, 'enabled'); // Enable strategy const configUpdate = {}; @@ -132,11 +128,7 @@ describe('Authentication', () => { assert.strictEqual(response.statusCode, 302); assert.strictEqual(response.headers.location, '/?authentication=disabled'); options.uri = - 'http://' + - global.oaeTests.tenants.localhost.host + - '/api/auth/' + - strategyName + - '/callback'; + 'http://' + global.oaeTests.tenants.localhost.host + '/api/auth/' + strategyName + '/callback'; options.method = method; request(options, (err, response, body) => { @@ -201,12 +193,7 @@ describe('Authentication', () => { strategyName, done => { const options = { - uri: - 'http://' + - global.oaeTests.tenants.localhost.host + - '/api/auth/' + - strategyName + - '/callback', + uri: 'http://' + global.oaeTests.tenants.localhost.host + '/api/auth/' + strategyName + '/callback', headers: { host: global.oaeTests.tenants.localhost.host }, @@ -249,6 +236,7 @@ describe('Authentication', () => { // so we can ignore this error and immediately move on to the next test return callback(); } + let msg = 'Did not expect an error message when reverting the localhost hostname.'; msg += 'This might cause failures further down the line.'; assert.fail(err.msg, '', msg); @@ -257,12 +245,9 @@ describe('Authentication', () => { ); // When we did update the test we need to wait untill the authentication strategies have been refreshed - AuthenticationAPI.emitter.once( - AuthenticationConstants.events.REFRESHED_STRATEGIES, - tenant => { - return callback(); - } - ); + AuthenticationAPI.emitter.once(AuthenticationConstants.events.REFRESHED_STRATEGIES, tenant => { + return callback(); + }); }); /** @@ -356,9 +341,7 @@ describe('Authentication', () => { 'google', done => { // Sanity check that Google is requesting authentication for localhost:2001 - let restContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.localhost.host - ); + let restContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.localhost.host); restContext.followRedirect = false; RestAPI.Authentication.googleRedirect(restContext, (err, body, response) => { assert.ok(!err); @@ -367,10 +350,7 @@ describe('Authentication', () => { assert.strictEqual(response.statusCode, 302); // Assert we're redirecting with the localhost:2001 hostname - let redirectUri = util.format( - 'http://%s/api/auth/google/callback', - global.oaeTests.tenants.localhost.host - ); + let redirectUri = util.format('http://%s/api/auth/google/callback', global.oaeTests.tenants.localhost.host); let parsedUrl = url.parse(response.headers.location, true); assert.strictEqual(parsedUrl.query.redirect_uri, redirectUri); @@ -387,33 +367,27 @@ describe('Authentication', () => { ); // Wait until the authentication api has finished refreshing its strategies - AuthenticationAPI.emitter.once( - AuthenticationConstants.events.REFRESHED_STRATEGIES, - tenant => { - // Assert we refreshed the strategies for the localhost tenant - assert.strictEqual(tenant.alias, global.oaeTests.tenants.localhost.alias); - assert.strictEqual(tenant.host, tenantUpdate.host); - - // Verify the authentication strategies are using the new tenant hostname - restContext = TestsUtil.createTenantRestContext(tenantUpdate.host); - restContext.followRedirect = false; - RestAPI.Authentication.googleRedirect(restContext, (err, body, response) => { - assert.ok(!err); + AuthenticationAPI.emitter.once(AuthenticationConstants.events.REFRESHED_STRATEGIES, tenant => { + // Assert we refreshed the strategies for the localhost tenant + assert.strictEqual(tenant.alias, global.oaeTests.tenants.localhost.alias); + assert.strictEqual(tenant.host, tenantUpdate.host); - // Assert a redirect - assert.strictEqual(response.statusCode, 302); + // Verify the authentication strategies are using the new tenant hostname + restContext = TestsUtil.createTenantRestContext(tenantUpdate.host); + restContext.followRedirect = false; + RestAPI.Authentication.googleRedirect(restContext, (err, body, response) => { + assert.ok(!err); - // Assert we're redirecting with the new hostname - redirectUri = util.format( - 'http://%s/api/auth/google/callback', - tenantUpdate.host - ); - parsedUrl = url.parse(response.headers.location, true); - assert.strictEqual(parsedUrl.query.redirect_uri, redirectUri); - return done(); - }); - } - ); + // Assert a redirect + assert.strictEqual(response.statusCode, 302); + + // Assert we're redirecting with the new hostname + redirectUri = util.format('http://%s/api/auth/google/callback', tenantUpdate.host); + parsedUrl = url.parse(response.headers.location, true); + assert.strictEqual(parsedUrl.query.redirect_uri, redirectUri); + return done(); + }); + }); }); }, callback @@ -428,79 +402,41 @@ describe('Authentication', () => { // Create a tenant and enable the external authentication strategies const tenantAlias = TenantsTestUtil.generateTestTenantAlias(); const tenantHost = TenantsTestUtil.generateTestTenantHost(); - TestsUtil.createTenantWithAdmin( - tenantAlias, - tenantHost, - (err, tenant, tenantAdminRestContext) => { - assert.ok(!err); - const config = { - 'oae-authentication/facebook/enabled': true, - 'oae-authentication/google/enabled': true - }; - AuthenticationTestUtil.assertUpdateAuthConfigSucceeds( - tenantAdminRestContext, - null, - config, - () => { - // Signing in without providing an email should fail - AuthenticationTestUtil.assertFacebookLoginFails( - tenant.host, - null, - 'email_missing', - () => { - // Signing in with an email address that does not belong to the configured - // email domain should fail - let email = TestsUtil.generateTestEmailAddress(); - AuthenticationTestUtil.assertFacebookLoginFails( - tenant.host, - email, - 'email_domain_mismatch', - () => { - // Signing in with an email address that does belong to the configured - // email domain should succeed - email = TestsUtil.generateTestEmailAddress( - null, - tenant.emailDomains[0] - ).toLowerCase(); - AuthenticationTestUtil.assertFacebookLoginSucceeds( - tenant.host, - { email }, - (restCtx, me) => { - assert.strictEqual(me.email, email); - - // Similarly, signing in through Google with an email address that does not match - // the configured email domain should result in an authentication failure - email = TestsUtil.generateTestEmailAddress(); - AuthenticationTestUtil.assertGoogleLoginFails( - tenant.host, - email, - 'email_domain_mismatch', - () => { - // Signing in with an email address that does belong to the configured - // email domain should succeed - email = TestsUtil.generateTestEmailAddress( - null, - tenant.emailDomains[0] - ).toLowerCase(); - AuthenticationTestUtil.assertGoogleLoginSucceeds( - tenant.host, - email, - (restCtx, me) => { - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + TestsUtil.createTenantWithAdmin(tenantAlias, tenantHost, (err, tenant, tenantAdminRestContext) => { + assert.ok(!err); + const config = { + 'oae-authentication/facebook/enabled': true, + 'oae-authentication/google/enabled': true + }; + AuthenticationTestUtil.assertUpdateAuthConfigSucceeds(tenantAdminRestContext, null, config, () => { + // Signing in without providing an email should fail + AuthenticationTestUtil.assertFacebookLoginFails(tenant.host, null, 'email_missing', () => { + // Signing in with an email address that does not belong to the configured + // email domain should fail + let email = TestsUtil.generateTestEmailAddress(); + AuthenticationTestUtil.assertFacebookLoginFails(tenant.host, email, 'email_domain_mismatch', () => { + // Signing in with an email address that does belong to the configured + // email domain should succeed + email = TestsUtil.generateTestEmailAddress(null, tenant.emailDomains[0]).toLowerCase(); + AuthenticationTestUtil.assertFacebookLoginSucceeds(tenant.host, { email }, (restCtx, me) => { + assert.strictEqual(me.email, email); + + // Similarly, signing in through Google with an email address that does not match + // the configured email domain should result in an authentication failure + email = TestsUtil.generateTestEmailAddress(); + AuthenticationTestUtil.assertGoogleLoginFails(tenant.host, email, 'email_domain_mismatch', () => { + // Signing in with an email address that does belong to the configured + // email domain should succeed + email = TestsUtil.generateTestEmailAddress(null, tenant.emailDomains[0]).toLowerCase(); + AuthenticationTestUtil.assertGoogleLoginSucceeds(tenant.host, email, (restCtx, me) => { + return callback(); + }); + }); + }); + }); + }); + }); + }); }); }); @@ -585,21 +521,18 @@ describe('Authentication', () => { } ); - AuthenticationAPI.emitter.once( - AuthenticationConstants.events.REFRESHED_STRATEGIES, - tenant => { - // Clear the email domain - TenantsTestUtil.updateTenantAndWait( - globalAdminRestContext, - global.oaeTests.tenants.localhost.alias, - { emailDomains: '' }, - err => { - assert.ok(!err); - return callback(); - } - ); - } - ); + AuthenticationAPI.emitter.once(AuthenticationConstants.events.REFRESHED_STRATEGIES, tenant => { + // Clear the email domain + TenantsTestUtil.updateTenantAndWait( + globalAdminRestContext, + global.oaeTests.tenants.localhost.alias, + { emailDomains: '' }, + err => { + assert.ok(!err); + return callback(); + } + ); + }); }); }); @@ -608,9 +541,7 @@ describe('Authentication', () => { */ it('verify forward to cas', callback => { _enableStrategy('cas', done => { - const restContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.localhost.host - ); + const restContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.localhost.host); restContext.followRedirect = false; RestAPI.Authentication.casRedirect(restContext, (err, body, response) => { assert.ok(!err); @@ -622,10 +553,7 @@ describe('Authentication', () => { assert.strictEqual(parseInt(casLocation.port, 10), port); assert.strictEqual(casLocation.pathname, '/cas/login'); assert.ok(casLocation.query); - assert.strictEqual( - casLocation.query.service, - 'http://localhost:2001/api/auth/cas/callback' - ); + assert.strictEqual(casLocation.query.service, 'http://localhost:2001/api/auth/cas/callback'); // Configure the login path const configUpdate = { 'oae-authentication/cas/loginPath': '/login/something/foo' }; @@ -645,10 +573,7 @@ describe('Authentication', () => { assert.strictEqual(parseInt(casLocation.port, 10), port); assert.strictEqual(casLocation.pathname, '/cas/login/something/foo'); assert.ok(casLocation.query); - assert.strictEqual( - casLocation.query.service, - 'http://localhost:2001/api/auth/cas/callback' - ); + assert.strictEqual(casLocation.query.service, 'http://localhost:2001/api/auth/cas/callback'); return callback(); }); } @@ -664,31 +589,21 @@ describe('Authentication', () => { _enableStrategy( 'cas', done => { - const restContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.localhost.host - ); + const restContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.localhost.host); restContext.followRedirect = false; - RestAPI.Authentication.casCallback( - restContext, - { ticket: 'invalid-ticket' }, - (err, body, response) => { + RestAPI.Authentication.casCallback(restContext, { ticket: 'invalid-ticket' }, (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 302); + assert.strictEqual(response.headers.location, '/?authentication=error'); + + // Try with a valid ticket + RestAPI.Authentication.casCallback(restContext, { ticket: validTicket }, (err, body, response) => { assert.ok(!err); assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/?authentication=error'); - - // Try with a valid ticket - RestAPI.Authentication.casCallback( - restContext, - { ticket: validTicket }, - (err, body, response) => { - assert.ok(!err); - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/'); - return done(); - } - ); - } - ); + assert.strictEqual(response.headers.location, '/'); + return done(); + }); + }); }, callback ); @@ -710,23 +625,14 @@ describe('Authentication', () => { 'cas', done => { // Trigger a ticket validation error by attempting to log in - const restContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.localhost.host - ); + const restContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.localhost.host); restContext.followRedirect = false; - RestAPI.Authentication.casCallback( - restContext, - { ticket: 'Someticket' }, - (err, body, response) => { - assert.ok(!err); - assert.strictEqual(response.statusCode, 302); - assert.strictEqual( - response.headers.location, - '/?authentication=failed&reason=tampering' - ); - return done(); - } - ); + RestAPI.Authentication.casCallback(restContext, { ticket: 'Someticket' }, (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 302); + assert.strictEqual(response.headers.location, '/?authentication=failed&reason=tampering'); + return done(); + }); }, callback ); @@ -742,30 +648,24 @@ describe('Authentication', () => { 'cas', done => { // Log in with our CAS server - const restContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.localhost.host - ); + const restContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.localhost.host); restContext.followRedirect = false; - RestAPI.Authentication.casCallback( - restContext, - { ticket: validTicket }, - (err, body, response) => { - assert.ok(!err); - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/'); + RestAPI.Authentication.casCallback(restContext, { ticket: validTicket }, (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 302); + assert.strictEqual(response.headers.location, '/'); - // Check that the attributes were parsed correctly - RestAPI.User.getMe(restContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.displayName, 'Simon'); - assert.strictEqual(me.email, email.toLowerCase()); - assert.strictEqual(me.authenticationStrategy, 'cas'); + // Check that the attributes were parsed correctly + RestAPI.User.getMe(restContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.displayName, 'Simon'); + assert.strictEqual(me.email, email.toLowerCase()); + assert.strictEqual(me.authenticationStrategy, 'cas'); - return done(); - }); - } - ); + return done(); + }); + }); }, callback ); @@ -781,30 +681,24 @@ describe('Authentication', () => { email = 'an invalid email address'; // Log in with our CAS server - const restContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.localhost.host - ); + const restContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.localhost.host); restContext.followRedirect = false; - RestAPI.Authentication.casCallback( - restContext, - { ticket: validTicket }, - (err, body, response) => { - assert.ok(!err); - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/'); + RestAPI.Authentication.casCallback(restContext, { ticket: validTicket }, (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 302); + assert.strictEqual(response.headers.location, '/'); - // Check that the attributes were parsed correctly - RestAPI.User.getMe(restContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.displayName, 'Simon'); - assert.strictEqual(me.authenticationStrategy, 'cas'); - assert.ok(!me.email); + // Check that the attributes were parsed correctly + RestAPI.User.getMe(restContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.displayName, 'Simon'); + assert.strictEqual(me.authenticationStrategy, 'cas'); + assert.ok(!me.email); - return done(); - }); - } - ); + return done(); + }); + }); }, callback ); @@ -826,31 +720,25 @@ describe('Authentication', () => { configUpdate, () => { // Log in with our CAS server, authentication should succeed - const restContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.localhost.host - ); + const restContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.localhost.host); restContext.followRedirect = false; - RestAPI.Authentication.casCallback( - restContext, - { ticket: validTicket }, - (err, body, response) => { - assert.ok(!err); - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/'); + RestAPI.Authentication.casCallback(restContext, { ticket: validTicket }, (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 302); + assert.strictEqual(response.headers.location, '/'); - // Check that the attributes were parsed correctly - RestAPI.User.getMe(restContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.authenticationStrategy, 'cas'); + // Check that the attributes were parsed correctly + RestAPI.User.getMe(restContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.authenticationStrategy, 'cas'); - // Nothing can be replaced from the attribute template, so we use it as-is - assert.strictEqual(me.displayName, '}displayname{'); + // Nothing can be replaced from the attribute template, so we use it as-is + assert.strictEqual(me.displayName, '}displayname{'); - return done(); - }); - } - ); + return done(); + }); + }); } ); }, @@ -874,46 +762,37 @@ describe('Authentication', () => { 'cas', done => { // Log in with our CAS server - const restContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.localhost.host - ); + const restContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.localhost.host); restContext.followRedirect = false; - RestAPI.Authentication.casCallback( - restContext, - { ticket: validTicket }, - (err, body, response) => { + RestAPI.Authentication.casCallback(restContext, { ticket: validTicket }, (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 302); + assert.strictEqual(response.headers.location, '/'); + + // Sanity check we're logged in + RestAPI.User.getMe(restContext, (err, me) => { assert.ok(!err); - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/'); + assert.ok(!me.anon); + assert.strictEqual(me.authenticationStrategy, 'cas'); - // Sanity check we're logged in - RestAPI.User.getMe(restContext, (err, me) => { + // Log out + RestAPI.Authentication.logout(restContext, (err, data, response) => { assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.authenticationStrategy, 'cas'); - // Log out - RestAPI.Authentication.logout(restContext, (err, data, response) => { - assert.ok(!err); - - // The user should be redirected to the CAS server - assert.strictEqual(response.statusCode, 302); - assert.ok(response.headers.location); - assert.strictEqual( - response.headers.location, - 'http://localhost:' + port + '/cas/logout' - ); + // The user should be redirected to the CAS server + assert.strictEqual(response.statusCode, 302); + assert.ok(response.headers.location); + assert.strictEqual(response.headers.location, 'http://localhost:' + port + '/cas/logout'); - // Sanity-check we're logged out - RestAPI.User.getMe(restContext, (err, me) => { - assert.ok(!err); - assert.ok(me.anon); - return done(); - }); + // Sanity-check we're logged out + RestAPI.User.getMe(restContext, (err, me) => { + assert.ok(!err); + assert.ok(me.anon); + return done(); }); }); - } - ); + }); + }); }, callback ); @@ -944,30 +823,24 @@ describe('Authentication', () => { assert.notStrictEqual(emailDomain, otherEmailDomain); email = TestsUtil.generateTestEmailAddress(null, otherEmailDomain); - const restContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.localhost.host - ); + const restContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.localhost.host); restContext.followRedirect = false; - RestAPI.Authentication.casCallback( - restContext, - { ticket: validTicket }, - (err, body, response) => { - assert.ok(!err); - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/'); + RestAPI.Authentication.casCallback(restContext, { ticket: validTicket }, (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 302); + assert.strictEqual(response.headers.location, '/'); - // Check that the attributes were parsed correctly - RestAPI.User.getMe(restContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.displayName, 'Simon'); - assert.strictEqual(me.email, email.toLowerCase()); - assert.strictEqual(me.authenticationStrategy, 'cas'); + // Check that the attributes were parsed correctly + RestAPI.User.getMe(restContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.displayName, 'Simon'); + assert.strictEqual(me.email, email.toLowerCase()); + assert.strictEqual(me.authenticationStrategy, 'cas'); - return done(); - }); - } - ); + return done(); + }); + }); } ); }, @@ -983,8 +856,7 @@ describe('Authentication', () => { beforeEach(callback => { // Setup the Shibboleth strategy (but do not enable it just yet) const configUpdate = {}; - configUpdate['oae-authentication/shibboleth/idpEntityID'] = - 'https://idp.example.com/shibboleth'; + configUpdate['oae-authentication/shibboleth/idpEntityID'] = 'https://idp.example.com/shibboleth'; return AuthenticationTestUtil.assertUpdateAuthConfigSucceeds( globalAdminRestContext, global.oaeTests.tenants.localhost.alias, @@ -1008,21 +880,18 @@ describe('Authentication', () => { } ); - AuthenticationAPI.emitter.once( - AuthenticationConstants.events.REFRESHED_STRATEGIES, - tenant => { - // Clear the email domain - TenantsTestUtil.updateTenantAndWait( - globalAdminRestContext, - global.oaeTests.tenants.localhost.alias, - { emailDomains: '' }, - err => { - assert.ok(!err); - return callback(); - } - ); - } - ); + AuthenticationAPI.emitter.once(AuthenticationConstants.events.REFRESHED_STRATEGIES, tenant => { + // Clear the email domain + TenantsTestUtil.updateTenantAndWait( + globalAdminRestContext, + global.oaeTests.tenants.localhost.alias, + { emailDomains: '' }, + err => { + assert.ok(!err); + return callback(); + } + ); + }); }); /** @@ -1036,54 +905,44 @@ describe('Authentication', () => { * @param {RestContext} callback.spRestContext The rest context that can be used on the service provider "tenant" */ const _initiateShibbolethAuthFlow = function(redirectUrl, callback) { - const tenantRestContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.localhost.host - ); + const tenantRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.localhost.host); tenantRestContext.followRedirect = false; - RestAPI.Authentication.shibbolethTenantRedirect( - tenantRestContext, - redirectUrl, - (err, body, response) => { + RestAPI.Authentication.shibbolethTenantRedirect(tenantRestContext, redirectUrl, (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 302); + + // Assert we're redirected to the SP host + const spHost = ShibbolethAPI.getSPHost(); + const location = url.parse(response.headers.location, true); + assert.strictEqual(location.host, spHost); + assert.strictEqual(location.pathname, '/api/auth/shibboleth/sp'); + + // Assert that we pass in the correct parameters + assert.ok(location.query); + assert.strictEqual(location.query.tenantAlias, global.oaeTests.tenants.localhost.alias); + assert.ok(location.query.signature); + assert.ok(location.query.expires); + + // Assert we can use this parameters with our SP and that it redirects us to the login handler + const spRestContext = TestsUtil.createTenantRestContext(spHost); + spRestContext.followRedirect = false; + const params = location.query; + RestAPI.Authentication.shibbolethSPRedirect(spRestContext, params, (err, body, response) => { assert.ok(!err); assert.strictEqual(response.statusCode, 302); - // Assert we're redirected to the SP host - const spHost = ShibbolethAPI.getSPHost(); + // Assert we're redirected to the proper Shibboleth handler const location = url.parse(response.headers.location, true); - assert.strictEqual(location.host, spHost); - assert.strictEqual(location.pathname, '/api/auth/shibboleth/sp'); + assert.strictEqual(location.pathname, '/Shibboleth.sso/Login'); - // Assert that we pass in the correct parameters + // Assert that we pass in the entity ID of the IdP and a target where the user should be redirected back to assert.ok(location.query); - assert.strictEqual(location.query.tenantAlias, global.oaeTests.tenants.localhost.alias); - assert.ok(location.query.signature); - assert.ok(location.query.expires); - - // Assert we can use this parameters with our SP and that it redirects us to the login handler - const spRestContext = TestsUtil.createTenantRestContext(spHost); - spRestContext.followRedirect = false; - const params = location.query; - RestAPI.Authentication.shibbolethSPRedirect( - spRestContext, - params, - (err, body, response) => { - assert.ok(!err); - assert.strictEqual(response.statusCode, 302); - - // Assert we're redirected to the proper Shibboleth handler - const location = url.parse(response.headers.location, true); - assert.strictEqual(location.pathname, '/Shibboleth.sso/Login'); + assert.strictEqual(location.query.entityID, 'https://idp.example.com/shibboleth'); + assert.strictEqual(location.query.target, '/api/auth/shibboleth/sp/returned'); - // Assert that we pass in the entity ID of the IdP and a target where the user should be redirected back to - assert.ok(location.query); - assert.strictEqual(location.query.entityID, 'https://idp.example.com/shibboleth'); - assert.strictEqual(location.query.target, '/api/auth/shibboleth/sp/returned'); - - return callback(tenantRestContext, spRestContext); - } - ); - } - ); + return callback(tenantRestContext, spRestContext); + }); + }); }; /** @@ -1105,41 +964,33 @@ describe('Authentication', () => { expectedRedirectUrl = expectedRedirectUrl || '/'; // The user returns from the Shibboleth IdP and arrives on our SP - RestAPI.Authentication.shibbolethSPCallback( - spRestContext, - attributes, - (err, body, response) => { + RestAPI.Authentication.shibbolethSPCallback(spRestContext, attributes, (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 302); + + // Assert that we're redirected back to the tenant + const location = url.parse(response.headers.location, true); + assert.strictEqual(location.host, global.oaeTests.tenants.localhost.host); + assert.strictEqual(location.pathname, '/api/auth/shibboleth/callback'); + + // Assert that the user id of the created user is present + assert.ok(location.query); + assert.ok(location.query.userId); + assert.ok(location.query.signature); + assert.ok(location.query.expires); + + // We arrive back at our tenant + const params = location.query; + RestAPI.Authentication.shibbolethTenantCallback(tenantRestContext, params, (err, body, response) => { assert.ok(!err); - assert.strictEqual(response.statusCode, 302); - - // Assert that we're redirected back to the tenant - const location = url.parse(response.headers.location, true); - assert.strictEqual(location.host, global.oaeTests.tenants.localhost.host); - assert.strictEqual(location.pathname, '/api/auth/shibboleth/callback'); - // Assert that the user id of the created user is present - assert.ok(location.query); - assert.ok(location.query.userId); - assert.ok(location.query.signature); - assert.ok(location.query.expires); - - // We arrive back at our tenant - const params = location.query; - RestAPI.Authentication.shibbolethTenantCallback( - tenantRestContext, - params, - (err, body, response) => { - assert.ok(!err); - - // We should be redirected to the specified redirect URL - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, expectedRedirectUrl); + // We should be redirected to the specified redirect URL + assert.strictEqual(response.statusCode, 302); + assert.strictEqual(response.headers.location, expectedRedirectUrl); - return callback(); - } - ); - } - ); + return callback(); + }); + }); }; /** @@ -1166,9 +1017,7 @@ describe('Authentication', () => { 'shib-session-id': Math.random(), // Fake some data about the IdP - 'persistent-id': - 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + - Math.random(), + 'persistent-id': 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + Math.random(), identityProvider: 'https://idp.example.com/shibboleth', affiliation: 'Digital Services', unscopedAffiliation: 'OAE Team', @@ -1184,24 +1033,18 @@ describe('Authentication', () => { }; // Perform the callback part of the authentication flow - _callbackShibbolethAuthFlow( - tenantRestContext, - spRestContext, - attributes, - '/content/bla', - () => { - // Assert we're logged in and the attributes were correctly persisted - RestAPI.User.getMe(tenantRestContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.authenticationStrategy, 'shibboleth'); - assert.strictEqual(me.displayName, 'Simon'); - assert.strictEqual(me.email, email.toLowerCase()); - assert.strictEqual(me.locale, 'en_UK'); - return done(); - }); - } - ); + _callbackShibbolethAuthFlow(tenantRestContext, spRestContext, attributes, '/content/bla', () => { + // Assert we're logged in and the attributes were correctly persisted + RestAPI.User.getMe(tenantRestContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.authenticationStrategy, 'shibboleth'); + assert.strictEqual(me.displayName, 'Simon'); + assert.strictEqual(me.email, email.toLowerCase()); + assert.strictEqual(me.locale, 'en_UK'); + return done(); + }); + }); }); }, callback @@ -1223,9 +1066,7 @@ describe('Authentication', () => { 'shib-session-id': Math.random(), // Fake some data about the IdP - 'persistent-id': - 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + - Math.random(), + 'persistent-id': 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + Math.random(), identityProvider: 'https://idp.example.com/shibboleth', affiliation: 'Digital Services', unscopedAffiliation: 'OAE Team', @@ -1240,24 +1081,18 @@ describe('Authentication', () => { }; // Perform the callback part of the authentication flow - _callbackShibbolethAuthFlow( - tenantRestContext, - spRestContext, - attributes, - '/content/bla', - () => { - // Assert we're logged in and the attributes were correctly persisted - RestAPI.User.getMe(tenantRestContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.authenticationStrategy, 'shibboleth'); - assert.strictEqual(me.displayName, attributes.remote_user); - assert.strictEqual(me.email, email.toLowerCase()); - assert.strictEqual(me.locale, 'en_UK'); - return done(); - }); - } - ); + _callbackShibbolethAuthFlow(tenantRestContext, spRestContext, attributes, '/content/bla', () => { + // Assert we're logged in and the attributes were correctly persisted + RestAPI.User.getMe(tenantRestContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.authenticationStrategy, 'shibboleth'); + assert.strictEqual(me.displayName, attributes.remote_user); + assert.strictEqual(me.email, email.toLowerCase()); + assert.strictEqual(me.locale, 'en_UK'); + return done(); + }); + }); }); }, callback @@ -1280,8 +1115,7 @@ describe('Authentication', () => { 'shib-session-id': Math.random(), // Fake some data about the IdP - 'persistent-id': - 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + Math.random(), + 'persistent-id': 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + Math.random(), identityProvider: 'https://idp.example.com/shibboleth', affiliation: 'Digital Services', unscopedAffiliation: 'OAE Team', @@ -1296,23 +1130,17 @@ describe('Authentication', () => { }; // Perform the callback part of the authentication flow - _callbackShibbolethAuthFlow( - tenantRestContext, - spRestContext, - attributes, - '/content/bla', - () => { - // Assert we're logged in and the attributes were correctly persisted - RestAPI.User.getMe(tenantRestContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.authenticationStrategy, 'shibboleth'); - assert.strictEqual(me.displayName, attributes.remote_user); - assert.strictEqual(me.visibility, 'private'); - callback(); - }); - } - ); + _callbackShibbolethAuthFlow(tenantRestContext, spRestContext, attributes, '/content/bla', () => { + // Assert we're logged in and the attributes were correctly persisted + RestAPI.User.getMe(tenantRestContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.authenticationStrategy, 'shibboleth'); + assert.strictEqual(me.displayName, attributes.remote_user); + assert.strictEqual(me.visibility, 'private'); + callback(); + }); + }); }); }; @@ -1327,24 +1155,15 @@ describe('Authentication', () => { // Profile should be made private if display name looks like a shibboleth identifier verifyInvalidDisplayNameMakesProfilePrivate('shibboleth!' + Math.random, () => { // Profile should be made private if display name is an email address - verifyInvalidDisplayNameMakesProfilePrivate( - TestsUtil.generateTestEmailAddress(), - () => { - // Profile should be made private if display name is a URL starting with https... - verifyInvalidDisplayNameMakesProfilePrivate( - 'https://idp.example.com/shibboleth', - () => { - // ...or http. - verifyInvalidDisplayNameMakesProfilePrivate( - 'http://example.tenant.com/profile', - () => { - return done(); - } - ); - } - ); - } - ); + verifyInvalidDisplayNameMakesProfilePrivate(TestsUtil.generateTestEmailAddress(), () => { + // Profile should be made private if display name is a URL starting with https... + verifyInvalidDisplayNameMakesProfilePrivate('https://idp.example.com/shibboleth', () => { + // ...or http. + verifyInvalidDisplayNameMakesProfilePrivate('http://example.tenant.com/profile', () => { + return done(); + }); + }); + }); }); }, callback @@ -1445,9 +1264,7 @@ describe('Authentication', () => { _enableStrategy( 'shibboleth', done => { - const restContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.localhost.host - ); + const restContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.localhost.host); restContext.followRedirect = false; // Missing or invalid parameters @@ -1524,19 +1341,15 @@ describe('Authentication', () => { }); spRestContext.cookieJar.setCookie(fakeCookie, 'http://localhost:2000/'); const attributes = {}; - RestAPI.Authentication.shibbolethSPCallback( - spRestContext, - attributes, - (err, body, response) => { - assert.strictEqual(err.code, 400); - - // The SP callback endpoint should not be exposed on regular tenants - RestAPI.Authentication.shibbolethSPCallback(tenantRestContext, {}, err => { - assert.strictEqual(err.code, 501); - return done(); - }); - } - ); + RestAPI.Authentication.shibbolethSPCallback(spRestContext, attributes, (err, body, response) => { + assert.strictEqual(err.code, 400); + + // The SP callback endpoint should not be exposed on regular tenants + RestAPI.Authentication.shibbolethSPCallback(tenantRestContext, {}, err => { + assert.strictEqual(err.code, 501); + return done(); + }); + }); }); }, callback @@ -1575,8 +1388,7 @@ describe('Authentication', () => { // Fake some data about the IdP 'persistent-id': - 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + - Math.random(), + 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + Math.random(), identityProvider: 'https://idp.example.com/shibboleth', // Generate an external id @@ -1593,23 +1405,17 @@ describe('Authentication', () => { }; // Perform the callback part of the authentication flow - _callbackShibbolethAuthFlow( - tenantRestContext, - spRestContext, - attributes, - '/content/bla', - () => { - // Because the user was already created in the system, - // we should NOT have created a new account. We can verify - // this by checking if the user's profile is unchanged - RestAPI.User.getMe(tenantRestContext, (err, me) => { - assert.ok(!err); - assert.strictEqual(me.displayName, 'Aron Viggo'); - assert.strictEqual(me.email, 'aron@institution.edu'); - return callback(); - }); - } - ); + _callbackShibbolethAuthFlow(tenantRestContext, spRestContext, attributes, '/content/bla', () => { + // Because the user was already created in the system, + // we should NOT have created a new account. We can verify + // this by checking if the user's profile is unchanged + RestAPI.User.getMe(tenantRestContext, (err, me) => { + assert.ok(!err); + assert.strictEqual(me.displayName, 'Aron Viggo'); + assert.strictEqual(me.email, 'aron@institution.edu'); + return callback(); + }); + }); }); } ); @@ -1629,9 +1435,7 @@ describe('Authentication', () => { _initiateShibbolethAuthFlow('/content/bla', (tenantRestContext, spRestContext) => { const attributes = { 'shib-session-id': _.random(100000), - 'persistent-id': - 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + - Math.random(), + 'persistent-id': 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + Math.random(), identityProvider: 'https://idp.example.com/shibboleth', affiliation: 'Digital Services', unscopedAffiliation: 'OAE Team', @@ -1644,64 +1448,48 @@ describe('Authentication', () => { email: TestsUtil.generateTestEmailAddress(), locale: 'en_UK' }; - _callbackShibbolethAuthFlow( - tenantRestContext, - spRestContext, - attributes, - '/content/bla', - () => { - RestAPI.User.getMe(tenantRestContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.authenticationStrategy, 'shibboleth'); - assert.strictEqual(me.displayName, 'Simon via displayname'); - assert.strictEqual(me.email, attributes.email.toLowerCase()); - assert.strictEqual(me.locale, 'en_UK'); - - // Verify that lower priority attributes are used when highest priority - // atrribute is not present - _initiateShibbolethAuthFlow( - '/content/bla', - (tenantRestContext, spRestContext) => { - const attributes = { - 'shib-session-id': _.random(100000), - 'persistent-id': - 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + - Math.random(), - identityProvider: 'https://idp.example.com/shibboleth', - affiliation: 'Digital Services', - unscopedAffiliation: 'OAE Team', - // eslint-disable-next-line camelcase - remote_user: 'nico' + _.random(100000), - - // Supply `cn`, which has a lower priority than `displayName`. - // Because `displayName` is not provided, `cn` will be used - cn: 'Nico via cn', - email: TestsUtil.generateTestEmailAddress(), - locale: 'en_UK' - }; - _callbackShibbolethAuthFlow( - tenantRestContext, - spRestContext, - attributes, - '/content/bla', - () => { - RestAPI.User.getMe(tenantRestContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.authenticationStrategy, 'shibboleth'); - assert.strictEqual(me.displayName, 'Nico via cn'); - assert.strictEqual(me.email, attributes.email.toLowerCase()); - assert.strictEqual(me.locale, 'en_UK'); - return done(); - }); - } - ); - } - ); + _callbackShibbolethAuthFlow(tenantRestContext, spRestContext, attributes, '/content/bla', () => { + RestAPI.User.getMe(tenantRestContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.authenticationStrategy, 'shibboleth'); + assert.strictEqual(me.displayName, 'Simon via displayname'); + assert.strictEqual(me.email, attributes.email.toLowerCase()); + assert.strictEqual(me.locale, 'en_UK'); + + // Verify that lower priority attributes are used when highest priority + // atrribute is not present + _initiateShibbolethAuthFlow('/content/bla', (tenantRestContext, spRestContext) => { + const attributes = { + 'shib-session-id': _.random(100000), + 'persistent-id': + 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + Math.random(), + identityProvider: 'https://idp.example.com/shibboleth', + affiliation: 'Digital Services', + unscopedAffiliation: 'OAE Team', + // eslint-disable-next-line camelcase + remote_user: 'nico' + _.random(100000), + + // Supply `cn`, which has a lower priority than `displayName`. + // Because `displayName` is not provided, `cn` will be used + cn: 'Nico via cn', + email: TestsUtil.generateTestEmailAddress(), + locale: 'en_UK' + }; + _callbackShibbolethAuthFlow(tenantRestContext, spRestContext, attributes, '/content/bla', () => { + RestAPI.User.getMe(tenantRestContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.authenticationStrategy, 'shibboleth'); + assert.strictEqual(me.displayName, 'Nico via cn'); + assert.strictEqual(me.email, attributes.email.toLowerCase()); + assert.strictEqual(me.locale, 'en_UK'); + return done(); + }); + }); }); - } - ); + }); + }); }); }, callback @@ -1719,9 +1507,7 @@ describe('Authentication', () => { _initiateShibbolethAuthFlow('/content/bla', (tenantRestContext, spRestContext) => { const attributes = { 'shib-session-id': _.random(100000), - 'persistent-id': - 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + - Math.random(), + 'persistent-id': 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + Math.random(), identityProvider: 'https://idp.example.com/shibboleth', affiliation: 'Digital Services', unscopedAffiliation: 'OAE Team', @@ -1734,64 +1520,48 @@ describe('Authentication', () => { eppn: TestsUtil.generateTestEmailAddress(), locale: 'en_UK' }; - _callbackShibbolethAuthFlow( - tenantRestContext, - spRestContext, - attributes, - '/content/bla', - () => { - RestAPI.User.getMe(tenantRestContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.authenticationStrategy, 'shibboleth'); - assert.strictEqual(me.displayName, 'Simon'); - assert.strictEqual(me.email, attributes.email.toLowerCase()); - assert.strictEqual(me.locale, 'en_UK'); - - // Verify that lower priority attributes are used when highest priority - // atrribute is not present - _initiateShibbolethAuthFlow( - '/content/bla', - (tenantRestContext, spRestContext) => { - const attributes = { - 'shib-session-id': _.random(100000), - 'persistent-id': - 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + - Math.random(), - identityProvider: 'https://idp.example.com/shibboleth', - affiliation: 'Digital Services', - unscopedAffiliation: 'OAE Team', - // eslint-disable-next-line camelcase - remote_user: 'simon' + _.random(100000), - - // Supply `eppn`, which has a lower priority than `email`. - // Because `email` is not provided, `eppn` will be used - displayname: 'Simon', - eppn: TestsUtil.generateTestEmailAddress(), - locale: 'en_UK' - }; - _callbackShibbolethAuthFlow( - tenantRestContext, - spRestContext, - attributes, - '/content/bla', - () => { - RestAPI.User.getMe(tenantRestContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.authenticationStrategy, 'shibboleth'); - assert.strictEqual(me.displayName, 'Simon'); - assert.strictEqual(me.email, attributes.eppn.toLowerCase()); - assert.strictEqual(me.locale, 'en_UK'); - return done(); - }); - } - ); - } - ); + _callbackShibbolethAuthFlow(tenantRestContext, spRestContext, attributes, '/content/bla', () => { + RestAPI.User.getMe(tenantRestContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.authenticationStrategy, 'shibboleth'); + assert.strictEqual(me.displayName, 'Simon'); + assert.strictEqual(me.email, attributes.email.toLowerCase()); + assert.strictEqual(me.locale, 'en_UK'); + + // Verify that lower priority attributes are used when highest priority + // atrribute is not present + _initiateShibbolethAuthFlow('/content/bla', (tenantRestContext, spRestContext) => { + const attributes = { + 'shib-session-id': _.random(100000), + 'persistent-id': + 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + Math.random(), + identityProvider: 'https://idp.example.com/shibboleth', + affiliation: 'Digital Services', + unscopedAffiliation: 'OAE Team', + // eslint-disable-next-line camelcase + remote_user: 'simon' + _.random(100000), + + // Supply `eppn`, which has a lower priority than `email`. + // Because `email` is not provided, `eppn` will be used + displayname: 'Simon', + eppn: TestsUtil.generateTestEmailAddress(), + locale: 'en_UK' + }; + _callbackShibbolethAuthFlow(tenantRestContext, spRestContext, attributes, '/content/bla', () => { + RestAPI.User.getMe(tenantRestContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.authenticationStrategy, 'shibboleth'); + assert.strictEqual(me.displayName, 'Simon'); + assert.strictEqual(me.email, attributes.eppn.toLowerCase()); + assert.strictEqual(me.locale, 'en_UK'); + return done(); + }); + }); }); - } - ); + }); + }); }); }, callback @@ -1809,9 +1579,7 @@ describe('Authentication', () => { _initiateShibbolethAuthFlow('/content/bla', (tenantRestContext, spRestContext) => { const attributes = { 'shib-session-id': _.random(100000), - 'persistent-id': - 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + - Math.random(), + 'persistent-id': 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + Math.random(), identityProvider: 'https://idp.example.com/shibboleth', affiliation: 'Digital Services', unscopedAffiliation: 'OAE Team', @@ -1824,64 +1592,48 @@ describe('Authentication', () => { locality: 'en_UK', locale: 'nl_BE' }; - _callbackShibbolethAuthFlow( - tenantRestContext, - spRestContext, - attributes, - '/content/bla', - () => { - RestAPI.User.getMe(tenantRestContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.authenticationStrategy, 'shibboleth'); - assert.strictEqual(me.displayName, 'Simon'); - assert.strictEqual(me.email, attributes.email.toLowerCase()); - assert.strictEqual(me.locale, attributes.locality); - - // Verify that lower priority attributes are used when highest priority - // atrribute is not present - _initiateShibbolethAuthFlow( - '/content/bla', - (tenantRestContext, spRestContext) => { - const attributes = { - 'shib-session-id': _.random(100000), - 'persistent-id': - 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + - Math.random(), - identityProvider: 'https://idp.example.com/shibboleth', - affiliation: 'Digital Services', - unscopedAffiliation: 'OAE Team', - // eslint-disable-next-line camelcase - remote_user: 'simon' + _.random(100000), - - // Supply `locale`, which has a lower priority than `locality`. - // Because `locality` is not provided, `locale` will be used - displayname: 'Simon', - eppn: TestsUtil.generateTestEmailAddress(), - locale: 'en_UK' - }; - _callbackShibbolethAuthFlow( - tenantRestContext, - spRestContext, - attributes, - '/content/bla', - () => { - RestAPI.User.getMe(tenantRestContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.authenticationStrategy, 'shibboleth'); - assert.strictEqual(me.displayName, 'Simon'); - assert.strictEqual(me.email, attributes.eppn.toLowerCase()); - assert.strictEqual(me.locale, attributes.locale); - return done(); - }); - } - ); - } - ); + _callbackShibbolethAuthFlow(tenantRestContext, spRestContext, attributes, '/content/bla', () => { + RestAPI.User.getMe(tenantRestContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.authenticationStrategy, 'shibboleth'); + assert.strictEqual(me.displayName, 'Simon'); + assert.strictEqual(me.email, attributes.email.toLowerCase()); + assert.strictEqual(me.locale, attributes.locality); + + // Verify that lower priority attributes are used when highest priority + // atrribute is not present + _initiateShibbolethAuthFlow('/content/bla', (tenantRestContext, spRestContext) => { + const attributes = { + 'shib-session-id': _.random(100000), + 'persistent-id': + 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + Math.random(), + identityProvider: 'https://idp.example.com/shibboleth', + affiliation: 'Digital Services', + unscopedAffiliation: 'OAE Team', + // eslint-disable-next-line camelcase + remote_user: 'simon' + _.random(100000), + + // Supply `locale`, which has a lower priority than `locality`. + // Because `locality` is not provided, `locale` will be used + displayname: 'Simon', + eppn: TestsUtil.generateTestEmailAddress(), + locale: 'en_UK' + }; + _callbackShibbolethAuthFlow(tenantRestContext, spRestContext, attributes, '/content/bla', () => { + RestAPI.User.getMe(tenantRestContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.authenticationStrategy, 'shibboleth'); + assert.strictEqual(me.displayName, 'Simon'); + assert.strictEqual(me.email, attributes.eppn.toLowerCase()); + assert.strictEqual(me.locale, attributes.locale); + return done(); + }); + }); }); - } - ); + }); + }); }); }, callback @@ -1898,9 +1650,7 @@ describe('Authentication', () => { _initiateShibbolethAuthFlow('/content/bla', (tenantRestContext, spRestContext) => { const attributes = { 'shib-session-id': _.random(10000), - 'persistent-id': - 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + - Math.random(), + 'persistent-id': 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + Math.random(), identityProvider: 'https://idp.example.com/shibboleth', affiliation: 'Digital Services', unscopedAffiliation: 'OAE Team', @@ -1912,29 +1662,23 @@ describe('Authentication', () => { email: TestsUtil.generateTestEmailAddress(), locale: 'Ohmygosh, I am like, totally, too_COOL' }; - _callbackShibbolethAuthFlow( - tenantRestContext, - spRestContext, - attributes, - '/content/bla', - () => { - RestAPI.User.getMe(tenantRestContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.authenticationStrategy, 'shibboleth'); - assert.strictEqual(me.displayName, 'Simon'); - assert.strictEqual(me.email, attributes.email.toLowerCase()); - assert.notStrictEqual(me.locale, attributes.locale); + _callbackShibbolethAuthFlow(tenantRestContext, spRestContext, attributes, '/content/bla', () => { + RestAPI.User.getMe(tenantRestContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.authenticationStrategy, 'shibboleth'); + assert.strictEqual(me.displayName, 'Simon'); + assert.strictEqual(me.email, attributes.email.toLowerCase()); + assert.notStrictEqual(me.locale, attributes.locale); - // Verify the user's locale defaulted to the tenant's default locale - RestAPI.Config.getTenantConfig(tenantRestContext, null, (err, config) => { - assert.ok(!err); - assert.strictEqual(me.locale, config['oae-principals'].user.defaultLanguage); - return done(); - }); + // Verify the user's locale defaulted to the tenant's default locale + RestAPI.Config.getTenantConfig(tenantRestContext, null, (err, config) => { + assert.ok(!err); + assert.strictEqual(me.locale, config['oae-principals'].user.defaultLanguage); + return done(); }); - } - ); + }); + }); }); }, callback @@ -1949,9 +1693,7 @@ describe('Authentication', () => { _initiateShibbolethAuthFlow('/content/bla', (tenantRestContext, spRestContext) => { const attributes = { 'shib-session-id': _.random(10000), - 'persistent-id': - 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + - Math.random(), + 'persistent-id': 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + Math.random(), identityProvider: 'https://idp.example.com/shibboleth', affiliation: 'Digital Services', unscopedAffiliation: 'OAE Team', @@ -1962,22 +1704,16 @@ describe('Authentication', () => { displayname: 'Simon', email: 'not a valid email address' }; - _callbackShibbolethAuthFlow( - tenantRestContext, - spRestContext, - attributes, - '/content/bla', - () => { - RestAPI.User.getMe(tenantRestContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.authenticationStrategy, 'shibboleth'); - assert.strictEqual(me.displayName, 'Simon'); - assert.ok(!me.email); - return callback(); - }); - } - ); + _callbackShibbolethAuthFlow(tenantRestContext, spRestContext, attributes, '/content/bla', () => { + RestAPI.User.getMe(tenantRestContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.authenticationStrategy, 'shibboleth'); + assert.strictEqual(me.displayName, 'Simon'); + assert.ok(!me.email); + return callback(); + }); + }); }); }); }); @@ -2009,8 +1745,7 @@ describe('Authentication', () => { const attributes = { 'shib-session-id': _.random(10000), 'persistent-id': - 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + - Math.random(), + 'https://idp.example.com/shibboleth#https://sp.example.com/shibboleth#' + Math.random(), identityProvider: 'https://idp.example.com/shibboleth', affiliation: 'Digital Services', unscopedAffiliation: 'OAE Team', @@ -2019,23 +1754,17 @@ describe('Authentication', () => { displayname: 'Simon', email }; - _callbackShibbolethAuthFlow( - tenantRestContext, - spRestContext, - attributes, - '/content/bla', - () => { - RestAPI.User.getMe(tenantRestContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.authenticationStrategy, 'shibboleth'); - assert.strictEqual(me.displayName, 'Simon'); - assert.strictEqual(me.email, attributes.email.toLowerCase()); + _callbackShibbolethAuthFlow(tenantRestContext, spRestContext, attributes, '/content/bla', () => { + RestAPI.User.getMe(tenantRestContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.authenticationStrategy, 'shibboleth'); + assert.strictEqual(me.displayName, 'Simon'); + assert.strictEqual(me.email, attributes.email.toLowerCase()); - return done(); - }); - } - ); + return done(); + }); + }); }); } ); @@ -2127,8 +1856,7 @@ describe('Authentication', () => { 'simon@bar.com', () => { AuthenticationTestUtil.assertGoogleLoginFails( - global.oaeTests.tenants.localhost - .host, + global.oaeTests.tenants.localhost.host, 'simon@baz.com', 'domain_not_allowed', () => { diff --git a/packages/oae-authentication/tests/test-oauth.js b/packages/oae-authentication/tests/test-oauth.js index 92b09b653b..6fb947cea0 100644 --- a/packages/oae-authentication/tests/test-oauth.js +++ b/packages/oae-authentication/tests/test-oauth.js @@ -13,15 +13,13 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const OAuth = require('oauth'); -const request = require('request'); +import assert from 'assert'; +import OAuth from 'oauth'; -const ConfigTestUtil = require('oae-config/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const TestsUtil = require('oae-tests'); +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import RestAPI from 'oae-rest'; +import { RestContext } from 'oae-rest/lib/model'; +import * as TestsUtil from 'oae-tests'; describe('Authentication', () => { // Rest context that can be used for anonymous requests on the localhost tenant @@ -40,9 +38,7 @@ describe('Authentication', () => { // Fill up global admin rest context globalAdminRestContext = TestsUtil.createGlobalAdminRestContext(); // Fill up anonymous localhost cam rest context - anonymousLocalRestContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.localhost.host - ); + anonymousLocalRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.localhost.host); // Fill up localhost tenant admin rest context RestAPI.Admin.loginOnTenant(globalAdminRestContext, 'localhost', null, (err, restContext) => { assert.ok(!err); @@ -68,52 +64,44 @@ describe('Authentication', () => { // Give simong an OAuth client const clientDisplayName = TestsUtil.generateRandomText(1); - RestAPI.OAuth.createClient( - localAdminRestContext, - simong.user.id, - clientDisplayName, - (err, client) => { - assert.ok(!err); - assert.strictEqual(client.displayName, clientDisplayName); - - // Setup an OAuth instance with which we can make oauth authenticated http calls - const oauth = new OAuth.OAuth2( - client.id, - client.secret, - 'http://' + global.oaeTests.tenants.localhost.host, - null, - '/api/auth/oauth/v2/token', - null - ); - oauth.getOAuthAccessToken( - '', + RestAPI.OAuth.createClient(localAdminRestContext, simong.user.id, clientDisplayName, (err, client) => { + assert.ok(!err); + assert.strictEqual(client.displayName, clientDisplayName); - // eslint-disable-next-line camelcase - { grant_type: 'client_credentials' }, - (err, accessToken, refreshToken, results) => { - assert.ok(!err); + // Setup an OAuth instance with which we can make oauth authenticated http calls + const oauth = new OAuth.OAuth2( + client.id, + client.secret, + 'http://' + global.oaeTests.tenants.localhost.host, + null, + '/api/auth/oauth/v2/token', + null + ); + oauth.getOAuthAccessToken( + '', - // Assert we retrieved an access token - assert.ok(accessToken); + // eslint-disable-next-line camelcase + { grant_type: 'client_credentials' }, + (err, accessToken, refreshToken, results) => { + assert.ok(!err); - // Assert we're not doing token expiration just yet - assert.ok(!refreshToken); + // Assert we retrieved an access token + assert.ok(accessToken); - // Ensure we're outputting OAuth compliant data - assert.ok(results.access_token); - assert.strictEqual(results.access_token, accessToken); + // Assert we're not doing token expiration just yet + assert.ok(!refreshToken); - // Generate a rest context that authenticates simong via OAuth - simong.oauthRestContext = _generateOAuthRestContext( - global.oaeTests.tenants.localhost.host, - accessToken - ); + // Ensure we're outputting OAuth compliant data + assert.ok(results.access_token); + assert.strictEqual(results.access_token, accessToken); - return callback(simong, client, accessToken); - } - ); - } - ); + // Generate a rest context that authenticates simong via OAuth + simong.oauthRestContext = _generateOAuthRestContext(global.oaeTests.tenants.localhost.host, accessToken); + + return callback(simong, client, accessToken); + } + ); + }); }); }; @@ -217,18 +205,12 @@ describe('Authentication', () => { assert.ok(!err); // Sanity check the piece of content has actually beenc reated - RestAPI.Content.getLibrary( - simong.oauthRestContext, - simong.user.id, - null, - null, - (err, data) => { - assert.ok(!err); - assert.strictEqual(data.results.length, 1); - assert.strictEqual(data.results[0].id, link.id); - return callback(); - } - ); + RestAPI.Content.getLibrary(simong.oauthRestContext, simong.user.id, null, null, (err, data) => { + assert.ok(!err); + assert.strictEqual(data.results.length, 1); + assert.strictEqual(data.results[0].id, link.id); + return callback(); + }); } ); }); @@ -293,55 +275,50 @@ describe('Authentication', () => { assert.ok(!accessToken); const clientDisplayName = TestsUtil.generateRandomText(1); - RestAPI.OAuth.createClient( - localAdminRestContext, - simong.user.id, - clientDisplayName, - (err, client) => { - assert.ok(!err); - assert.ok(!accessToken); + RestAPI.OAuth.createClient(localAdminRestContext, simong.user.id, clientDisplayName, (err, client) => { + assert.ok(!err); + assert.ok(!accessToken); - // Assert that just the ID is not sufficient - oauth = new OAuth.OAuth2( - client.id, - 'Fake secret', - 'http://' + global.oaeTests.tenants.localhost.host, - null, - '/api/auth/oauth/v2/token', - null - ); - oauth.getOAuthAccessToken( - '', - // eslint-disable-next-line camelcase - { grant_type: 'client_credentials' }, - (err, accessToken, refreshToken, results) => { - assert.strictEqual(err.statusCode, 401); - assert.ok(!accessToken); - - // Assert that just the secret is not sufficient - oauth = new OAuth.OAuth2( - 'Fake ID', - client.secret, - 'http://' + global.oaeTests.tenants.localhost.host, - null, - '/api/auth/oauth/v2/token', - null - ); - oauth.getOAuthAccessToken( - '', - // eslint-disable-next-line camelcase - { grant_type: 'client_credentials' }, - (err, accessToken, refreshToken, results) => { - assert.strictEqual(err.statusCode, 401); - assert.ok(!accessToken); - - return callback(); - } - ); - } - ); - } - ); + // Assert that just the ID is not sufficient + oauth = new OAuth.OAuth2( + client.id, + 'Fake secret', + 'http://' + global.oaeTests.tenants.localhost.host, + null, + '/api/auth/oauth/v2/token', + null + ); + oauth.getOAuthAccessToken( + '', + // eslint-disable-next-line camelcase + { grant_type: 'client_credentials' }, + (err, accessToken, refreshToken, results) => { + assert.strictEqual(err.statusCode, 401); + assert.ok(!accessToken); + + // Assert that just the secret is not sufficient + oauth = new OAuth.OAuth2( + 'Fake ID', + client.secret, + 'http://' + global.oaeTests.tenants.localhost.host, + null, + '/api/auth/oauth/v2/token', + null + ); + oauth.getOAuthAccessToken( + '', + // eslint-disable-next-line camelcase + { grant_type: 'client_credentials' }, + (err, accessToken, refreshToken, results) => { + assert.strictEqual(err.statusCode, 401); + assert.ok(!accessToken); + + return callback(); + } + ); + } + ); + }); } ); }); @@ -358,36 +335,21 @@ describe('Authentication', () => { assert.ok(!err); // Invalid userId - RestAPI.OAuth.createClient( - localAdminRestContext, - 'invalid user', - 'By admin', - (err, client) => { - assert.strictEqual(err.code, 400); + RestAPI.OAuth.createClient(localAdminRestContext, 'invalid user', 'By admin', (err, client) => { + assert.strictEqual(err.code, 400); - // Missing displayName - RestAPI.OAuth.createClient( - localAdminRestContext, - simong.user.id, - null, - (err, client) => { - assert.strictEqual(err.code, 400); + // Missing displayName + RestAPI.OAuth.createClient(localAdminRestContext, simong.user.id, null, (err, client) => { + assert.strictEqual(err.code, 400); - // Sanity check - RestAPI.OAuth.createClient( - localAdminRestContext, - simong.user.id, - 'Test app', - (err, client) => { - assert.ok(!err); + // Sanity check + RestAPI.OAuth.createClient(localAdminRestContext, simong.user.id, 'Test app', (err, client) => { + assert.ok(!err); - return callback(); - } - ); - } - ); - } - ); + return callback(); + }); + }); + }); }); }); @@ -399,66 +361,42 @@ describe('Authentication', () => { assert.ok(!err); // Assert that anonymous users can't create clients - RestAPI.OAuth.createClient( - anonymousLocalRestContext, - simong.user.id, - 'By anon', - (err, client) => { + RestAPI.OAuth.createClient(anonymousLocalRestContext, simong.user.id, 'By anon', (err, client) => { + assert.strictEqual(err.code, 401); + assert.ok(!client); + + // Assert that non-admins cannot create clients + RestAPI.OAuth.createClient(simong.restContext, simong.user.id, 'By simong', (err, client) => { assert.strictEqual(err.code, 401); assert.ok(!client); - // Assert that non-admins cannot create clients - RestAPI.OAuth.createClient( - simong.restContext, - simong.user.id, - 'By simong', - (err, client) => { + // Make Nico a tenant admin on the `localhost` tenant + // We can't use the localAdminRestContext as that is really the global admin on the localhost tenant + RestAPI.User.setTenantAdmin(globalAdminRestContext, nico.user.id, true, err => { + assert.ok(!err); + + // As Nico is not a tenant on the `camtest` tenant we cannot create an OAuth application for that user + RestAPI.OAuth.createClient(nico.restContext, 'u:camtest:foo', 'By admin', (err, client) => { assert.strictEqual(err.code, 401); - assert.ok(!client); - // Make Nico a tenant admin on the `localhost` tenant - // We can't use the localAdminRestContext as that is really the global admin on the localhost tenant - RestAPI.User.setTenantAdmin(globalAdminRestContext, nico.user.id, true, err => { + // Assert that tenant admins can create a client + RestAPI.OAuth.createClient(localAdminRestContext, simong.user.id, 'By admin', (err, client) => { assert.ok(!err); + assert.ok(client); - // As Nico is not a tenant on the `camtest` tenant we cannot create an OAuth application for that user - RestAPI.OAuth.createClient( - nico.restContext, - 'u:camtest:foo', - 'By admin', - (err, client) => { - assert.strictEqual(err.code, 401); - - // Assert that tenant admins can create a client - RestAPI.OAuth.createClient( - localAdminRestContext, - simong.user.id, - 'By admin', - (err, client) => { - assert.ok(!err); - assert.ok(client); - - // Sanity check the client was associated with simong - RestAPI.OAuth.getClients( - simong.restContext, - simong.user.id, - (err, data) => { - assert.ok(!err); - assert.strictEqual(data.results.length, 1); - assert.strictEqual(data.results[0].displayName, 'By admin'); + // Sanity check the client was associated with simong + RestAPI.OAuth.getClients(simong.restContext, simong.user.id, (err, data) => { + assert.ok(!err); + assert.strictEqual(data.results.length, 1); + assert.strictEqual(data.results[0].displayName, 'By admin'); - return callback(); - } - ); - } - ); - } - ); + return callback(); + }); }); - } - ); - } - ); + }); + }); + }); + }); }); }); }); @@ -618,93 +556,63 @@ describe('Authentication', () => { assert.ok(!updateClient); // Sanity check the client hasn't been updated - RestAPI.OAuth.getClients( - localAdminRestContext, - simong.user.id, - (err, data) => { - assert.ok(!err); - assert.strictEqual(data.results.length, 1); - assert.strictEqual(data.results[0].id, client.id); - assert.strictEqual(data.results[0].displayName, client.displayName); - assert.strictEqual(data.results[0].secret, client.secret); - - // Update the client - RestAPI.OAuth.updateClient( - simong.restContext, - simong.user.id, - client.id, - 'New name by user', - 'New secret by user', - (err, updateClient) => { + RestAPI.OAuth.getClients(localAdminRestContext, simong.user.id, (err, data) => { + assert.ok(!err); + assert.strictEqual(data.results.length, 1); + assert.strictEqual(data.results[0].id, client.id); + assert.strictEqual(data.results[0].displayName, client.displayName); + assert.strictEqual(data.results[0].secret, client.secret); + + // Update the client + RestAPI.OAuth.updateClient( + simong.restContext, + simong.user.id, + client.id, + 'New name by user', + 'New secret by user', + (err, updateClient) => { + assert.ok(!err); + assert.strictEqual(updateClient.id, client.id); + assert.strictEqual(updateClient.displayName, 'New name by user'); + assert.strictEqual(updateClient.secret, 'New secret by user'); + + // Verify that it's been updated + RestAPI.OAuth.getClients(simong.restContext, simong.user.id, (err, data) => { assert.ok(!err); - assert.strictEqual(updateClient.id, client.id); - assert.strictEqual(updateClient.displayName, 'New name by user'); - assert.strictEqual(updateClient.secret, 'New secret by user'); + assert.strictEqual(data.results.length, 1); + assert.strictEqual(data.results[0].id, client.id); + assert.strictEqual(data.results[0].displayName, 'New name by user'); + assert.strictEqual(data.results[0].secret, 'New secret by user'); - // Verify that it's been updated - RestAPI.OAuth.getClients( - simong.restContext, + // Verify that a tenant admin can update a client for a user local to his tenant + RestAPI.OAuth.updateClient( + localAdminRestContext, simong.user.id, - (err, data) => { + client.id, + 'New name by admin', + 'New secret by admin', + (err, updateClient) => { assert.ok(!err); - assert.strictEqual(data.results.length, 1); - assert.strictEqual(data.results[0].id, client.id); - assert.strictEqual( - data.results[0].displayName, - 'New name by user' - ); - assert.strictEqual( - data.results[0].secret, - 'New secret by user' - ); - - // Verify that a tenant admin can update a client for a user local to his tenant - RestAPI.OAuth.updateClient( - localAdminRestContext, - simong.user.id, - client.id, - 'New name by admin', - 'New secret by admin', - (err, updateClient) => { - assert.ok(!err); - assert.strictEqual(updateClient.id, client.id); - assert.strictEqual( - updateClient.displayName, - 'New name by admin' - ); - assert.strictEqual( - updateClient.secret, - 'New secret by admin' - ); - - // Verify that it's been updated - RestAPI.OAuth.getClients( - simong.restContext, - simong.user.id, - (err, data) => { - assert.ok(!err); - assert.strictEqual(data.results.length, 1); - assert.strictEqual(data.results[0].id, client.id); - assert.strictEqual( - data.results[0].displayName, - 'New name by admin' - ); - assert.strictEqual( - data.results[0].secret, - 'New secret by admin' - ); - - return callback(); - } - ); - } - ); + assert.strictEqual(updateClient.id, client.id); + assert.strictEqual(updateClient.displayName, 'New name by admin'); + assert.strictEqual(updateClient.secret, 'New secret by admin'); + + // Verify that it's been updated + RestAPI.OAuth.getClients(simong.restContext, simong.user.id, (err, data) => { + assert.ok(!err); + assert.strictEqual(data.results.length, 1); + assert.strictEqual(data.results[0].id, client.id); + assert.strictEqual(data.results[0].displayName, 'New name by admin'); + assert.strictEqual(data.results[0].secret, 'New secret by admin'); + + return callback(); + }); } ); - } - ); - } - ); + }); + } + ); + }); } ); } @@ -723,16 +631,11 @@ describe('Authentication', () => { it('verify validation', callback => { _setupOAuth((simong, client, accessToken) => { // Try deleting an unknown client - RestAPI.OAuth.deleteClient( - simong.restContext, - simong.user.id, - 'unknown client id', - (err, data) => { - assert.strictEqual(err.code, 404); + RestAPI.OAuth.deleteClient(simong.restContext, simong.user.id, 'unknown client id', (err, data) => { + assert.strictEqual(err.code, 404); - return callback(); - } - ); + return callback(); + }); }); }); @@ -746,99 +649,67 @@ describe('Authentication', () => { assert.ok(!err); // Verify an anonymous user cannot delete a client - RestAPI.OAuth.deleteClient( - anonymousLocalRestContext, - simong.user.id, - client.id, - (err, data) => { + RestAPI.OAuth.deleteClient(anonymousLocalRestContext, simong.user.id, client.id, (err, data) => { + assert.strictEqual(err.code, 401); + + // Verify you cannot delete another user their client + RestAPI.OAuth.deleteClient(nico.restContext, simong.user.id, client.id, (err, data) => { assert.strictEqual(err.code, 401); - // Verify you cannot delete another user their client - RestAPI.OAuth.deleteClient( - nico.restContext, - simong.user.id, - client.id, - (err, data) => { - assert.strictEqual(err.code, 401); + // Verify that a tenant admin cannot delete a client for a user that is not from his tenant + RestAPI.OAuth.deleteClient(camAdminRestContext, simong.user.id, client.id, (err, data) => { + assert.strictEqual(err.code, 401); - // Verify that a tenant admin cannot delete a client for a user that is not from his tenant - RestAPI.OAuth.deleteClient( - camAdminRestContext, - simong.user.id, - client.id, - (err, data) => { - assert.strictEqual(err.code, 401); + // Sanity check the client is still there + RestAPI.OAuth.getClients(localAdminRestContext, simong.user.id, (err, data) => { + assert.ok(!err); + assert.strictEqual(data.results.length, 1); + assert.strictEqual(data.results[0].id, client.id); + assert.strictEqual(data.results[0].displayName, client.displayName); + assert.strictEqual(data.results[0].secret, client.secret); - // Sanity check the client is still there - RestAPI.OAuth.getClients( + // Delete the client + RestAPI.OAuth.deleteClient(simong.restContext, simong.user.id, client.id, (err, data) => { + assert.ok(!err); + + // Verify that it's been removed + RestAPI.OAuth.getClients(simong.restContext, simong.user.id, (err, data) => { + assert.ok(!err); + assert.strictEqual(data.results.length, 0); + + // Create another client and try to delete it as the tenant admin + const clientDisplayName = TestsUtil.generateRandomText(1); + RestAPI.OAuth.createClient( localAdminRestContext, simong.user.id, - (err, data) => { + clientDisplayName, + (err, client) => { assert.ok(!err); - assert.strictEqual(data.results.length, 1); - assert.strictEqual(data.results[0].id, client.id); - assert.strictEqual(data.results[0].displayName, client.displayName); - assert.strictEqual(data.results[0].secret, client.secret); - - // Delete the client + assert.strictEqual(client.displayName, clientDisplayName); RestAPI.OAuth.deleteClient( - simong.restContext, + localAdminRestContext, simong.user.id, client.id, (err, data) => { assert.ok(!err); // Verify that it's been removed - RestAPI.OAuth.getClients( - simong.restContext, - simong.user.id, - (err, data) => { - assert.ok(!err); - assert.strictEqual(data.results.length, 0); - - // Create another client and try to delete it as the tenant admin - const clientDisplayName = TestsUtil.generateRandomText(1); - RestAPI.OAuth.createClient( - localAdminRestContext, - simong.user.id, - clientDisplayName, - (err, client) => { - assert.ok(!err); - assert.strictEqual(client.displayName, clientDisplayName); - RestAPI.OAuth.deleteClient( - localAdminRestContext, - simong.user.id, - client.id, - (err, data) => { - assert.ok(!err); - - // Verify that it's been removed - RestAPI.OAuth.getClients( - simong.restContext, - simong.user.id, - (err, data) => { - assert.ok(!err); - assert.strictEqual(data.results.length, 0); - - return callback(); - } - ); - } - ); - } - ); - } - ); + RestAPI.OAuth.getClients(simong.restContext, simong.user.id, (err, data) => { + assert.ok(!err); + assert.strictEqual(data.results.length, 0); + + return callback(); + }); } ); } ); - } - ); - } - ); - } - ); + }); + }); + }); + }); + }); + }); }); }); }); diff --git a/packages/oae-authentication/tests/test-shibboleth-migration.js b/packages/oae-authentication/tests/test-shibboleth-migration.js index e17e0dc0fa..16b850397a 100644 --- a/packages/oae-authentication/tests/test-shibboleth-migration.js +++ b/packages/oae-authentication/tests/test-shibboleth-migration.js @@ -13,17 +13,19 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const util = require('util'); -const _ = require('underscore'); -const csv = require('csv'); -const temp = require('temp'); - -const Cassandra = require('oae-util/lib/cassandra'); -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); -const log = require('oae-logger').logger('oae-authentication'); -const ShibbolethMigrator = require('../../../etc/migration/shibboleth_migration/migrate-users-to-shibboleth.js'); +import assert from 'assert'; +import util from 'util'; +import _ from 'underscore'; +import csv from 'csv'; +import temp from 'temp'; + +import Cassandra from 'oae-util/lib/cassandra'; +import RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import { logger } from 'oae-logger'; +import ShibbolethMigrator from '../../../etc/migration/shibboleth_migration/migrate-users-to-shibboleth.js'; + +const log = logger('oae-authentication'); describe('Shibboleth Migration', () => { let camAdminRestContext = null; @@ -57,13 +59,13 @@ describe('Shibboleth Migration', () => { }); /*! - * Check that Shibboleth login ID records were created for all users with Google login - * - * @param String tenantAlias The tenant we are testing - * @param {Object[]} users The users we want to check login for - * @param {Function} callback Invoked when assertions are complete - * @throws {AssertionError} Thrown if the assertions fail - */ + * Check that Shibboleth login ID records were created for all users with Google login + * + * @param String tenantAlias The tenant we are testing + * @param {Object[]} users The users we want to check login for + * @param {Function} callback Invoked when assertions are complete + * @throws {AssertionError} Thrown if the assertions fail + */ const _assertHaveShibbolethLoginIds = function(tenantAlias, users, callback) { if (_.isEmpty(users)) { return callback(); @@ -91,13 +93,13 @@ describe('Shibboleth Migration', () => { }; /*! - * Check that no Shibboleth login ID records were created for users without a Google login ID - * - * @param String tenantAlias The tenant we are testing - * @param {Object[]} users The users we want to check login for - * @param {Function} callback Invoked when assertions are complete - * @throws {AssertionError} Thrown if the assertions fail - */ + * Check that no Shibboleth login ID records were created for users without a Google login ID + * + * @param String tenantAlias The tenant we are testing + * @param {Object[]} users The users we want to check login for + * @param {Function} callback Invoked when assertions are complete + * @throws {AssertionError} Thrown if the assertions fail + */ const _assertHaveNoShibbolethLoginIds = function(tenantAlias, users, callback) { if (_.isEmpty(users)) { return callback(); @@ -120,12 +122,12 @@ describe('Shibboleth Migration', () => { }; /*! - * Create Google authentication records for a set of users - * - * @param String tenantAlias The tenant we are testing - * @param {Object[]} users The users we want to check login for - * @return {Object[]} queries The Cassandra queries to create the records - */ + * Create Google authentication records for a set of users + * + * @param String tenantAlias The tenant we are testing + * @param {Object[]} users The users we want to check login for + * @return {Object[]} queries The Cassandra queries to create the records + */ const _createGoogleLogins = function(tenantAlias, users) { // Create Google logins for users const googleLoginIds = _.map(users, user => { @@ -139,8 +141,7 @@ describe('Shibboleth Migration', () => { .map(googleLoginId => { return [ { - query: - 'INSERT INTO "AuthenticationUserLoginId" ("loginId", "userId", "value") VALUES (?, ?, ?)', + query: 'INSERT INTO "AuthenticationUserLoginId" ("loginId", "userId", "value") VALUES (?, ?, ?)', parameters: [googleLoginId.loginId, googleLoginId.userId, '1'] }, { diff --git a/packages/oae-authentication/tests/test-signed.js b/packages/oae-authentication/tests/test-signed.js index 0a51fca0e3..29dd947552 100644 --- a/packages/oae-authentication/tests/test-signed.js +++ b/packages/oae-authentication/tests/test-signed.js @@ -13,23 +13,14 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const url = require('url'); -const util = require('util'); -const request = require('request'); -const _ = require('underscore'); - -const ConfigTestUtil = require('oae-config/lib/test/util'); -const { Context } = require('oae-context'); -const PrincipalsAPI = require('oae-principals'); -const RestAPI = require('oae-rest'); -const RestUtil = require('oae-rest/lib/util'); -const { RestContext } = require('oae-rest/lib/model'); -const TestsUtil = require('oae-tests'); - -const AuthenticationAPI = require('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const { LoginId } = require('oae-authentication/lib/model'); +import assert from 'assert'; +import url from 'url'; +import util from 'util'; +import _ from 'underscore'; + +import RestAPI from 'oae-rest'; +import { RestContext } from 'oae-rest/lib/model'; +import * as TestsUtil from 'oae-tests'; const _originalDateNow = Date.now; @@ -47,9 +38,7 @@ describe('Authentication', () => { */ before(() => { // Instantiate the rest contexts we'll use for these tests - anonymousTenantRestContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.localhost.host - ); + anonymousTenantRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.localhost.host); anonymousGlobalRestContext = TestsUtil.createGlobalRestContext(); globalAdminRestContext = TestsUtil.createGlobalAdminRestContext(); camAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.cam.host); @@ -67,19 +56,14 @@ describe('Authentication', () => { describe('Signed Authentication', () => { /*! - * Tries to login to the signed authentication endpoint by using the provided parameters. - * - * @param {String} requestInfoUrl The url from the request info object granted by an authentication grant endpoint - * @param {Object} body The signed body to use to authenticate to the signed authentication endpoint - * @param {Boolean} isLoggedIn Whether or not the user should be logged in - * @param {Function} callback Standard callback function - */ - const _performSignedAuthenticationRequest = function( - requestInfoUrl, - body, - isLoggedIn, - callback - ) { + * Tries to login to the signed authentication endpoint by using the provided parameters. + * + * @param {String} requestInfoUrl The url from the request info object granted by an authentication grant endpoint + * @param {Object} body The signed body to use to authenticate to the signed authentication endpoint + * @param {Boolean} isLoggedIn Whether or not the user should be logged in + * @param {Function} callback Standard callback function + */ + const _performSignedAuthenticationRequest = function(requestInfoUrl, body, isLoggedIn, callback) { const parsedUrl = url.parse(requestInfoUrl); const restCtx = new RestContext('http://' + global.oaeTests.tenants.localhost.host, { hostHeader: parsedUrl.host @@ -172,37 +156,33 @@ describe('Authentication', () => { */ it('verify parameter validation', callback => { // Ensure wen cannot get a signed request with no tenant alias - RestAPI.Admin.getSignedTenantAuthenticationRequestInfo( - globalAdminRestContext, - null, - (err, requestInfo) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.ok(!requestInfo); - - // Ensure we cannot get a signed request with a non-existing tenant alias - RestAPI.Admin.getSignedTenantAuthenticationRequestInfo( - globalAdminRestContext, - 'some non existing tenant alias', - (err, requestInfo) => { - assert.ok(err); - assert.strictEqual(err.code, 404); - assert.ok(!requestInfo); + RestAPI.Admin.getSignedTenantAuthenticationRequestInfo(globalAdminRestContext, null, (err, requestInfo) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.ok(!requestInfo); - // Sanity check that we can get a signed request with an existing tenant alias - RestAPI.Admin.getSignedTenantAuthenticationRequestInfo( - globalAdminRestContext, - 'localhost', - (err, requestInfo) => { - assert.ok(!err); - assert.ok(requestInfo); - return callback(); - } - ); - } - ); - } - ); + // Ensure we cannot get a signed request with a non-existing tenant alias + RestAPI.Admin.getSignedTenantAuthenticationRequestInfo( + globalAdminRestContext, + 'some non existing tenant alias', + (err, requestInfo) => { + assert.ok(err); + assert.strictEqual(err.code, 404); + assert.ok(!requestInfo); + + // Sanity check that we can get a signed request with an existing tenant alias + RestAPI.Admin.getSignedTenantAuthenticationRequestInfo( + globalAdminRestContext, + 'localhost', + (err, requestInfo) => { + assert.ok(!err); + assert.ok(requestInfo); + return callback(); + } + ); + } + ); + }); }); }); @@ -252,68 +232,54 @@ describe('Authentication', () => { assert.ok(!requestInfo); // Make nico a tenant administrator for the cam tenant - RestAPI.User.setTenantAdmin( - camAdminRestContext, - nico.user.id, - true, - err => { - assert.ok(!err); - - // Verify a tenant administrator cannot impersonate another tenant administrator - RestAPI.Admin.getSignedBecomeUserAuthenticationRequestInfo( - camAdminRestContext, - nico.user.id, - (err, requestInfo) => { - assert.ok(err); - assert.strictEqual(err.code, 401); - assert.ok(!requestInfo); - - // Verify a global administrator can impersonate a tenant administrator - RestAPI.Admin.getSignedBecomeUserAuthenticationRequestInfo( - globalAdminRestContext, - nico.user.id, - (err, requestInfo) => { - assert.ok(!err); - assert.ok(requestInfo); - assert.strictEqual( - requestInfo.body.becomeUserId, - nico.user.id - ); - - // Verify a global administrator can impersonate a regular user - RestAPI.Admin.getSignedBecomeUserAuthenticationRequestInfo( - globalAdminRestContext, - mrvisser.user.id, - (err, requestInfo) => { - assert.ok(!err); - assert.ok(requestInfo); - assert.strictEqual( - requestInfo.body.becomeUserId, - mrvisser.user.id - ); - - // Verify a tenant administrator can impersonate a regular user (nico was made a tenant administrator earlier in the test) - RestAPI.Admin.getSignedBecomeUserAuthenticationRequestInfo( - nico.restContext, - mrvisser.user.id, - (err, requestInfo) => { - assert.ok(!err); - assert.ok(requestInfo); - assert.strictEqual( - requestInfo.body.becomeUserId, - mrvisser.user.id - ); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + RestAPI.User.setTenantAdmin(camAdminRestContext, nico.user.id, true, err => { + assert.ok(!err); + + // Verify a tenant administrator cannot impersonate another tenant administrator + RestAPI.Admin.getSignedBecomeUserAuthenticationRequestInfo( + camAdminRestContext, + nico.user.id, + (err, requestInfo) => { + assert.ok(err); + assert.strictEqual(err.code, 401); + assert.ok(!requestInfo); + + // Verify a global administrator can impersonate a tenant administrator + RestAPI.Admin.getSignedBecomeUserAuthenticationRequestInfo( + globalAdminRestContext, + nico.user.id, + (err, requestInfo) => { + assert.ok(!err); + assert.ok(requestInfo); + assert.strictEqual(requestInfo.body.becomeUserId, nico.user.id); + + // Verify a global administrator can impersonate a regular user + RestAPI.Admin.getSignedBecomeUserAuthenticationRequestInfo( + globalAdminRestContext, + mrvisser.user.id, + (err, requestInfo) => { + assert.ok(!err); + assert.ok(requestInfo); + assert.strictEqual(requestInfo.body.becomeUserId, mrvisser.user.id); + + // Verify a tenant administrator can impersonate a regular user (nico was made a tenant administrator earlier in the test) + RestAPI.Admin.getSignedBecomeUserAuthenticationRequestInfo( + nico.restContext, + mrvisser.user.id, + (err, requestInfo) => { + assert.ok(!err); + assert.ok(requestInfo); + assert.strictEqual(requestInfo.body.becomeUserId, mrvisser.user.id); + return callback(); + } + ); + } + ); + } + ); + } + ); + }); } ); } @@ -475,41 +441,31 @@ describe('Authentication', () => { // Ensure that authentication with this request data works _performSignedAuthenticationRequest(requestInfo.url, requestInfo.body, true, () => { // Permutations of missing parameters - _performSignedAuthenticationRequest( - requestInfo.url, - _.omit(requestInfo.body, 'userId'), - false, - () => { + _performSignedAuthenticationRequest(requestInfo.url, _.omit(requestInfo.body, 'userId'), false, () => { + _performSignedAuthenticationRequest(requestInfo.url, _.omit(requestInfo.body, 'expires'), false, () => { _performSignedAuthenticationRequest( requestInfo.url, - _.omit(requestInfo.body, 'expires'), + _.omit(requestInfo.body, 'signature'), false, () => { + // All parameters are present, but some are invalid _performSignedAuthenticationRequest( requestInfo.url, - _.omit(requestInfo.body, 'signature'), + _.extend({}, requestInfo.body, { userId: 'u:admin:badid' }), false, () => { - // All parameters are present, but some are invalid _performSignedAuthenticationRequest( requestInfo.url, - _.extend({}, requestInfo.body, { userId: 'u:admin:badid' }), + _.extend({}, requestInfo.body, { expires: 1234567890 }), false, () => { - _performSignedAuthenticationRequest( + return _performSignedAuthenticationRequest( requestInfo.url, - _.extend({}, requestInfo.body, { expires: 1234567890 }), + _.extend({}, requestInfo.body, { + signature: 'bad signature' + }), false, - () => { - return _performSignedAuthenticationRequest( - requestInfo.url, - _.extend({}, requestInfo.body, { - signature: 'bad signature' - }), - false, - callback - ); - } + callback ); } ); @@ -517,8 +473,8 @@ describe('Authentication', () => { ); } ); - } - ); + }); + }); }); } ); @@ -540,12 +496,7 @@ describe('Authentication', () => { return now + 5 * 60 * 1000; }; - return _performSignedAuthenticationRequest( - requestInfo.url, - requestInfo.body, - false, - callback - ); + return _performSignedAuthenticationRequest(requestInfo.url, requestInfo.body, false, callback); } ); }); @@ -599,64 +550,59 @@ describe('Authentication', () => { // Ensure the request data can be used to authentication when the body is untampered with _performSignedAuthenticationRequest(requestInfo.url, requestInfo.body, true, () => { // Permutations of missing parameters - _performSignedAuthenticationRequest( - requestInfo.url, - _.omit(requestInfo.body, 'userId'), - false, - () => { - _performSignedAuthenticationRequest( - requestInfo.url, - _.omit(requestInfo.body, 'expires'), - false, - () => { - _performSignedAuthenticationRequest( - requestInfo.url, - _.omit(requestInfo.body, 'signature'), - false, - () => { - _performSignedAuthenticationRequest( - requestInfo.url, - _.omit(requestInfo.body, 'becomeUserId'), - false, - () => { - // Permutations of tampered data - _performSignedAuthenticationRequest( - requestInfo.url, - _.extend(requestInfo.body, { userId: simon.user.id }), - false, - () => { - _performSignedAuthenticationRequest( - requestInfo.url, - _.extend(requestInfo.body, { expires: 1234567890 }), - false, - () => { - _performSignedAuthenticationRequest( - requestInfo.url, - _.extend(requestInfo.body, { signature: 'different' }), - false, - () => { - return _performSignedAuthenticationRequest( - requestInfo.url, - _.extend(requestInfo.body, { - becomeUserId: simon.user.id - }), - false, - callback - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + _performSignedAuthenticationRequest(requestInfo.url, _.omit(requestInfo.body, 'userId'), false, () => { + _performSignedAuthenticationRequest( + requestInfo.url, + _.omit(requestInfo.body, 'expires'), + false, + () => { + _performSignedAuthenticationRequest( + requestInfo.url, + _.omit(requestInfo.body, 'signature'), + false, + () => { + _performSignedAuthenticationRequest( + requestInfo.url, + _.omit(requestInfo.body, 'becomeUserId'), + false, + () => { + // Permutations of tampered data + _performSignedAuthenticationRequest( + requestInfo.url, + _.extend(requestInfo.body, { userId: simon.user.id }), + false, + () => { + _performSignedAuthenticationRequest( + requestInfo.url, + _.extend(requestInfo.body, { expires: 1234567890 }), + false, + () => { + _performSignedAuthenticationRequest( + requestInfo.url, + _.extend(requestInfo.body, { signature: 'different' }), + false, + () => { + return _performSignedAuthenticationRequest( + requestInfo.url, + _.extend(requestInfo.body, { + becomeUserId: simon.user.id + }), + false, + callback + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); }); } ); @@ -682,12 +628,7 @@ describe('Authentication', () => { return now + 5 * 60 * 1000; }; - return _performSignedAuthenticationRequest( - requestInfo.url, - requestInfo.body, - false, - callback - ); + return _performSignedAuthenticationRequest(requestInfo.url, requestInfo.body, false, callback); } ); }); diff --git a/packages/oae-authentication/tests/test-util.js b/packages/oae-authentication/tests/test-util.js index dbd287e042..11e16bb19e 100644 --- a/packages/oae-authentication/tests/test-util.js +++ b/packages/oae-authentication/tests/test-util.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const AuthenticationUtil = require('oae-authentication/lib/util'); +import * as AuthenticationUtil from 'oae-authentication/lib/util'; describe('Authentication - util', () => { describe('#setProfileParameter', () => { @@ -30,12 +30,7 @@ describe('Authentication - util', () => { initial: true }; const profileParameterName = 'displayName'; - AuthenticationUtil.setProfileParameter( - profileParameters, - profileParameterName, - template, - data - ); + AuthenticationUtil.setProfileParameter(profileParameters, profileParameterName, template, data); // Assert the displayName was added assert.strictEqual(profileParameters.displayName, 'John Doe'); diff --git a/packages/oae-authz/lib/activity.js b/packages/oae-authz/lib/activity.js index 9bc3c89a75..fd1e0d80d4 100644 --- a/packages/oae-authz/lib/activity.js +++ b/packages/oae-authz/lib/activity.js @@ -13,15 +13,15 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const ActivityAPI = require('oae-activity'); -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityModel = require('oae-activity/lib/model'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const ResourceActions = require('oae-resource/lib/actions'); -const { ResourceConstants } = require('oae-resource/lib/constants'); -const TenantsAPI = require('oae-tenants'); +import * as ActivityAPI from 'oae-activity'; +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as ActivityModel from 'oae-activity/lib/model'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as ResourceActions from 'oae-resource/lib/actions'; +import { ResourceConstants } from 'oae-resource/lib/constants'; +import * as TenantsAPI from 'oae-tenants'; /// /////////////// // EMAIL ENTITY // @@ -116,13 +116,9 @@ ActivityAPI.registerActivityEntityType('email', { * Register the "self" association for the email, which specifies only the email resource itself as * a potentital recipient */ -ActivityAPI.registerActivityEntityAssociation( - 'email', - 'self', - (associationsCtx, entity, callback) => { - return callback(null, [entity[ActivityConstants.properties.OAE_ID]]); - } -); +ActivityAPI.registerActivityEntityAssociation('email', 'self', (associationsCtx, entity, callback) => { + return callback(null, [entity[ActivityConstants.properties.OAE_ID]]); +}); /// ////////////////// // INVITE ACTIVITY // diff --git a/packages/oae-authz/lib/api.js b/packages/oae-authz/lib/api.js index fbe77ece68..5c3d90d420 100644 --- a/packages/oae-authz/lib/api.js +++ b/packages/oae-authz/lib/api.js @@ -13,19 +13,21 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const Cassandra = require('oae-util/lib/cassandra'); -const OaeUtil = require('oae-util/lib/util'); -const TenantsUtil = require('oae-tenants/lib/util'); +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as TenantsUtil from 'oae-tenants/lib/util'; -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzGraph = require('oae-authz/lib/internal/graph'); -const AuthzUtil = require('oae-authz/lib/util'); -const { Validator } = require('oae-authz/lib/validator'); +import { AuthzConstants } from 'oae-authz/lib/constants'; +import AuthzGraph from 'oae-authz/lib/internal/graph'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import { Validator } from 'oae-authz/lib/validator'; -const log = require('oae-logger').logger('oae-authz-api'); +import { logger } from 'oae-logger'; + +const log = logger('oae-authz-api'); /// ////////////////////// // ROLES & PERMISSIONS // @@ -129,6 +131,7 @@ const getAllRoles = function(principalId, resourceId, callback) { if (directRole) { roles.push(directRole); } + callback(null, roles); }); }); @@ -151,6 +154,7 @@ const _getIndirectRoles = function(principalId, resourceId, callback) { if (err) { return callback(err); } + if (_.isEmpty(groups)) { return callback(null, []); } @@ -266,6 +270,7 @@ const _hasRole = function(principalId, resourceId, role, callback) { if (err) { return callback(err); } + if (directRole && (role === null || directRole === role)) { return callback(null, true); } @@ -281,10 +286,12 @@ const _hasRole = function(principalId, resourceId, role, callback) { return callback(null, true); // If we are looking for a specific role and that specific role is present } + if (_.contains(roles, role)) { return callback(null, true); // If the specified role cannot be found } + callback(null, false); }); }); @@ -311,6 +318,7 @@ const updateRoles = function(resourceId, changes, callback) { validator.check(principalId, { code: 400, msg: 'Invalid principal id specified: ' + principalId }).isPrincipalId(); validator.check(changes[principalId], { code: 400, msg: 'Invalid role provided' }).isValidRoleChange(); } + if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -348,6 +356,7 @@ const _updateRoles = function(resourceId, changes, callback) { } ]; } + if (role === false) { // A role has been removed, so we remove it from both the roles and inverse // members index @@ -362,6 +371,7 @@ const _updateRoles = function(resourceId, changes, callback) { } ]; } + return []; }) .flatten() @@ -729,6 +739,7 @@ const getPrincipalMembershipsGraph = function(principalId, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { // If we have no cached memberships, go to the memberships CFs to try and resolve it // recursively @@ -819,6 +830,7 @@ const getPrincipalMemberships = function(principalId, start, limit, callback) { if (err) { return callback(err); } + if (startMatched || !_.isEmpty(rows)) { // If we received some groups from the memberships cache, it means it is valid and we can // use the data we have @@ -847,6 +859,7 @@ const getPrincipalMemberships = function(principalId, start, limit, callback) { // We don't want to include the start element, so pick the next element as the start startIndex = _.indexOf(allMemberships, start) + 1; } + const memberships = allMemberships.slice(startIndex, startIndex + limit); nextToken = null; @@ -942,6 +955,7 @@ const _getIndirectPrincipalMembershipsFromCache = function(principalId, start, l if (err) { return callback(err); } + if (startMatched || !_.isEmpty(rows)) { // If we received some groups from the memberships cache, it means it is valid and we // can use the data we have @@ -1231,6 +1245,7 @@ const getRolesForPrincipalsAndResourceType = function(principalIds, resourceType const principalId = principalIds[i]; validator.check(principalId, { code: 400, msg: 'Invalid principal id specified: ' + principalId }).isPrincipalId(); } + if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -1321,14 +1336,17 @@ const resolveEffectiveRole = function(user, resource, rolesPriority, callback) { if (err) { return callback(err); } + if (implicitRole === _.last(rolesPriority)) { // We already have the highest role, use it return callback(null, implicitRole, canInteract); } + if (!user) { // We are anonymous so cannot have any explicit access or interact. Use only our implicitRole if we have one return callback(null, implicitRole, canInteract, implicitRole); } + if (AuthzUtil.isUserId(resource.id)) { // No explicit association exists from a user to another user, therefore we can use the implicit result return callback(null, implicitRole, canInteract); @@ -1339,6 +1357,7 @@ const resolveEffectiveRole = function(user, resource, rolesPriority, callback) { if (err) { return callback(err); } + if (_.isEmpty(roles)) { // We have no explicit role, so we fall back to the implicit access return callback(null, implicitRole, canInteract); @@ -1397,6 +1416,7 @@ const resolveImplicitRole = function(principal, resource, rolesPriority, callbac // resource, but no interaction abilities return callback(null, _.first(rolesPriority), false); } + // Anonymous has no implicit access on loggedin or private items return callback(); } @@ -1410,6 +1430,7 @@ const resolveImplicitRole = function(principal, resource, rolesPriority, callbac // The user themself has highest implicit access on themself return callback(null, _.last(rolesPriority), true); } + if (principal.isAdmin(resource.tenant.alias)) { // An admin of the resource's tenant has highest implicit access on the resource return callback(null, _.last(rolesPriority), true); @@ -1431,6 +1452,7 @@ const resolveImplicitRole = function(principal, resource, rolesPriority, callbac // interact with it if their tenants are interactable return callback(null, _.first(rolesPriority), tenantsCanInteract); } + // The resource is not public if ( AuthzUtil.isUserId(principalId) && @@ -1442,17 +1464,19 @@ const resolveImplicitRole = function(principal, resource, rolesPriority, callbac // share something with a private, joinable group of which I am not a member) return callback(null, _.first(rolesPriority), tenantsCanInteract); } + if (resource.visibility === AuthzConstants.visibility.LOGGEDIN && principal.tenant.alias === resource.tenant.alias) { // A principal has lowest implicit role and can view a loggedin, non-joinable // resource only if they are logged in to its tenant return callback(null, _.first(rolesPriority), true); } + // The resource is private and not joinable, and the principal is not an admin user, // therefore there is no way we can grant any implicit access on this resource return callback(); }; -module.exports = { +export { getDirectRoles, getAllRoles, hasAnyRole, diff --git a/packages/oae-authz/lib/constants.js b/packages/oae-authz/lib/constants.js index 7fc6a5b6ca..7039e86822 100644 --- a/packages/oae-authz/lib/constants.js +++ b/packages/oae-authz/lib/constants.js @@ -85,4 +85,4 @@ AuthzConstants.activity = { ACTIVITY_INVITE: 'invite' }; -module.exports = { AuthzConstants }; +export { AuthzConstants }; diff --git a/packages/oae-authz/lib/delete.js b/packages/oae-authz/lib/delete.js index f3c48578a6..1285fe556e 100644 --- a/packages/oae-authz/lib/delete.js +++ b/packages/oae-authz/lib/delete.js @@ -13,10 +13,12 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const Cassandra = require('oae-util/lib/cassandra'); -const log = require('oae-logger').logger('authz-delete'); +import { logger } from 'oae-logger'; +import * as Cassandra from 'oae-util/lib/cassandra'; + +const log = logger('authz-delete'); /** * Indicate that the provided `resourceId` has been deleted @@ -30,10 +32,7 @@ const setDeleted = function(resourceId, callback) { callback || function(err) { if (err) { - return log().error( - { err, resourceId }, - 'An error occurred while trying to set a resource as deleted' - ); + return log().error({ err, resourceId }, 'An error occurred while trying to set a resource as deleted'); } }; @@ -56,18 +55,11 @@ const unsetDeleted = function(resourceId, callback) { callback || function(err) { if (err) { - return log().error( - { err, resourceId }, - 'An error occurred while trying to unset a resource as deleted' - ); + return log().error({ err, resourceId }, 'An error occurred while trying to unset a resource as deleted'); } }; - return Cassandra.runQuery( - 'DELETE FROM "AuthzDeleted" WHERE "resourceId" = ?', - [resourceId], - callback - ); + return Cassandra.runQuery('DELETE FROM "AuthzDeleted" WHERE "resourceId" = ?', [resourceId], callback); }; /** @@ -107,8 +99,4 @@ const isDeleted = function(resourceIds, callback) { ); }; -module.exports = { - setDeleted, - unsetDeleted, - isDeleted -}; +export { setDeleted, unsetDeleted, isDeleted }; diff --git a/packages/oae-authz/lib/init.js b/packages/oae-authz/lib/init.js index c3ed6a4404..d2efec7b14 100644 --- a/packages/oae-authz/lib/init.js +++ b/packages/oae-authz/lib/init.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -// eslint-disable-next-line no-unused-vars -const AuthzActivity = require('./activity'); -const AuthzSearch = require('./search'); +import * as AuthzSearch from './search'; +// eslint-disable-next-line no-unused-vars, import/namespace +import * as AuthzActivity from './activity'; -module.exports = function(config, callback) { +export function init(config, callback) { return AuthzSearch.init(callback); -}; +} diff --git a/packages/oae-authz/lib/internal/graph.js b/packages/oae-authz/lib/internal/graph.js index 8bb9eb8d3e..e29c978ea7 100644 --- a/packages/oae-authz/lib/internal/graph.js +++ b/packages/oae-authz/lib/internal/graph.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); -const { Graph } = require('data-structures'); +import util from 'util'; +import _ from 'underscore'; +import { Graph } from 'data-structures'; /** * AuthzGraph inherits from the data-structures Graph and provides some additional data and function @@ -24,6 +24,7 @@ const { Graph } = require('data-structures'); const AuthzGraph = function() { Graph.call(this); }; + util.inherits(AuthzGraph, Graph); /** @@ -150,13 +151,7 @@ AuthzGraph.prototype.traverseOut = function(nodeId) { * @return {Node[]} The array of nodes that are visited while traversing the graph * @api private */ -AuthzGraph.prototype._traverse = function( - nodeId, - getEdgeFn, - nextNodeProperty, - _nodes, - _visitedIds -) { +AuthzGraph.prototype._traverse = function(nodeId, getEdgeFn, nextNodeProperty, _nodes, _visitedIds) { const self = this; _nodes = _nodes || []; @@ -182,4 +177,4 @@ AuthzGraph.prototype._traverse = function( return _nodes; }; -module.exports = AuthzGraph; +export default AuthzGraph; diff --git a/packages/oae-authz/lib/invitations/dao.js b/packages/oae-authz/lib/invitations/dao.js index cfa7f52af4..1c5c8b09ae 100644 --- a/packages/oae-authz/lib/invitations/dao.js +++ b/packages/oae-authz/lib/invitations/dao.js @@ -13,13 +13,13 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); -const Chance = require('chance'); +import util from 'util'; +import _ from 'underscore'; +import Chance from 'chance'; -const Cassandra = require('oae-util/lib/cassandra'); +import * as Cassandra from 'oae-util/lib/cassandra'; -const { Validator } = require('oae-authz/lib/validator'); +import { Validator } from 'oae-authz/lib/validator'; const chance = new Chance(); @@ -85,24 +85,20 @@ const getOrCreateTokensByEmails = function(emails, callback) { */ const getTokensByEmails = function(emails, callback) { // Get all existing tokens for emails - Cassandra.runQuery( - 'SELECT * FROM "AuthzInvitationsTokenByEmail" WHERE "email" IN ?', - [emails], - (err, rows) => { - if (err) { - return callback(err); - } - - const emailTokens = _.chain(rows) - .map(Cassandra.rowToHash) - .indexBy('email') - .mapObject(hash => { - return hash.token; - }) - .value(); - return callback(null, emailTokens); + Cassandra.runQuery('SELECT * FROM "AuthzInvitationsTokenByEmail" WHERE "email" IN ?', [emails], (err, rows) => { + if (err) { + return callback(err); } - ); + + const emailTokens = _.chain(rows) + .map(Cassandra.rowToHash) + .indexBy('email') + .mapObject(hash => { + return hash.token; + }) + .value(); + return callback(null, emailTokens); + }); }; /** @@ -114,29 +110,26 @@ const getTokensByEmails = function(emails, callback) { * @param {String} callback.email The email that was associated to the token */ const getEmailByToken = function(token, callback) { - Cassandra.runQuery( - 'SELECT * FROM "AuthzInvitationsEmailByToken" WHERE "token" = ?', - [token], - (err, rows) => { - if (err) { - return callback(err); - } - if (_.isEmpty(rows)) { - return callback({ - code: 404, - msg: util.format('There is no email associated to the email token "%s"', token) - }); - } - - const email = _.chain(rows) - .map(Cassandra.rowToHash) - .pluck('email') - .first() - .value(); + Cassandra.runQuery('SELECT * FROM "AuthzInvitationsEmailByToken" WHERE "token" = ?', [token], (err, rows) => { + if (err) { + return callback(err); + } - return callback(null, email); + if (_.isEmpty(rows)) { + return callback({ + code: 404, + msg: util.format('There is no email associated to the email token "%s"', token) + }); } - ); + + const email = _.chain(rows) + .map(Cassandra.rowToHash) + .pluck('email') + .first() + .value(); + + return callback(null, email); + }); }; /** @@ -175,6 +168,7 @@ const getAllInvitationsByResourceId = function(resourceId, callback, _invitation if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback(null, _invitations); } @@ -207,6 +201,7 @@ const getInvitation = function(resourceId, email, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback({ code: 404, @@ -232,16 +227,10 @@ const getInvitation = function(resourceId, email, callback) { */ const createInvitations = function(resourceId, emailRoles, inviterUserId, callback) { const validator = new Validator(); - validator - .check(resourceId, { code: 400, msg: 'Specified resource must have a valid resource id' }) - .isResourceId(); + validator.check(resourceId, { code: 400, msg: 'Specified resource must have a valid resource id' }).isResourceId(); _.each(emailRoles, (role, email) => { - validator - .check(email, { code: 400, msg: 'A valid email must be supplied to invite' }) - .isEmail(); - validator - .check(role, { code: 400, msg: 'A valid role must be supplied to give the invited user' }) - .isValidRole(); + validator.check(email, { code: 400, msg: 'A valid email must be supplied to invite' }).isEmail(); + validator.check(role, { code: 400, msg: 'A valid role must be supplied to give the invited user' }).isValidRole(); }); validator .check(inviterUserId, { @@ -279,8 +268,7 @@ const createInvitations = function(resourceId, emailRoles, inviterUserId, callba parameters: [hash.inviterUserId, hash.role, hash.resourceId, hash.email] }, { - query: - 'INSERT INTO "AuthzInvitationsResourceIdByEmail" ("resourceId", "email") VALUES (?, ?)', + query: 'INSERT INTO "AuthzInvitationsResourceIdByEmail" ("resourceId", "email") VALUES (?, ?)', parameters: [hash.resourceId, hash.email] } ]; @@ -307,20 +295,14 @@ const createInvitations = function(resourceId, emailRoles, inviterUserId, callba */ const updateInvitationRoles = function(resourceId, emailRoles, callback) { const validator = new Validator(); - validator - .check(resourceId, { code: 400, msg: 'Specified resource must have a valid resource id' }) - .isResourceId(); + validator.check(resourceId, { code: 400, msg: 'Specified resource must have a valid resource id' }).isResourceId(); _.each(emailRoles, (role, email) => { - validator - .check(email, { code: 400, msg: util.format('Invalid email "%s" specified', email) }) - .isEmail(); + validator.check(email, { code: 400, msg: util.format('Invalid email "%s" specified', email) }).isEmail(); validator .check(role, { code: 400, msg: util.format('Invalid role change "%s" specified', role) }) .isValidRoleChange(); if (role !== false) { - validator - .check(null, { code: 400, msg: util.format('Invalid role "%s" specified', role) }) - .isString(role); + validator.check(null, { code: 400, msg: util.format('Invalid role "%s" specified', role) }).isString(role); } }); if (validator.hasErrors()) { @@ -341,8 +323,7 @@ const updateInvitationRoles = function(resourceId, emailRoles, callback) { parameters: [resourceId, email] }, { - query: - 'DELETE FROM "AuthzInvitationsResourceIdByEmail" WHERE "email" = ? AND "resourceId" = ?', + query: 'DELETE FROM "AuthzInvitationsResourceIdByEmail" WHERE "email" = ? AND "resourceId" = ?', parameters: [email, resourceId] } ); @@ -459,6 +440,7 @@ const _getAllInvitationResourceIdsByEmail = function(email, callback, _resourceI if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback(null, _resourceIds); } @@ -502,7 +484,7 @@ const _getInvitations = function(resourceIds, email, callback) { ); }; -module.exports = { +export { getOrCreateTokensByEmails, getTokensByEmails, getEmailByToken, diff --git a/packages/oae-authz/lib/invitations/index.js b/packages/oae-authz/lib/invitations/index.js index f9bcfc10d7..4b78047160 100644 --- a/packages/oae-authz/lib/invitations/index.js +++ b/packages/oae-authz/lib/invitations/index.js @@ -13,15 +13,15 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const PrincipalsUtil = require('oae-principals/lib/util'); +import * as PrincipalsUtil from 'oae-principals/lib/util'; -const AuthzInvitationsDAO = require('oae-authz/lib/invitations/dao'); -const AuthzPermissions = require('oae-authz/lib/permissions'); -const AuthzUtil = require('oae-authz/lib/util'); +import * as AuthzInvitationsDAO from 'oae-authz/lib/invitations/dao'; +import * as AuthzPermissions from 'oae-authz/lib/permissions'; +import * as AuthzUtil from 'oae-authz/lib/util'; -const { Invitation } = require('oae-authz/lib/invitations/model'); +import { Invitation } from 'oae-authz/lib/invitations/model'; /** * Get all the invitations for the specified resource @@ -79,6 +79,4 @@ const _invitationsFromHashes = function(ctx, resource, invitationHashes, callbac }); }; -module.exports = { - getAllInvitations -}; +export { getAllInvitations }; diff --git a/packages/oae-authz/lib/invitations/util.js b/packages/oae-authz/lib/invitations/util.js index cf3a1068f8..78fe73a8fe 100644 --- a/packages/oae-authz/lib/invitations/util.js +++ b/packages/oae-authz/lib/invitations/util.js @@ -13,13 +13,13 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const OaeUtil = require('oae-util/lib/util'); +import * as OaeUtil from 'oae-util/lib/util'; -const AuthzInvitationsDAO = require('oae-authz/lib/invitations/dao'); -const AuthzModel = require('oae-authz/lib/model'); -const AuthzUtil = require('oae-authz/lib/util'); +import * as AuthzInvitationsDAO from 'oae-authz/lib/invitations/dao'; +import * as AuthzModel from 'oae-authz/lib/model'; +import * as AuthzUtil from 'oae-authz/lib/util'; /** * Given an authz resource id and proposed email role changes, compute the EmailChangeInfo object @@ -57,6 +57,4 @@ const computeInvitationRolesAfterChanges = function(authzResourceId, changes, op ); }; -module.exports = { - computeInvitationRolesAfterChanges -}; +export { computeInvitationRolesAfterChanges }; diff --git a/packages/oae-authz/lib/migration.js b/packages/oae-authz/lib/migration.js index 5a7f823cb1..8636ca2a69 100644 --- a/packages/oae-authz/lib/migration.js +++ b/packages/oae-authz/lib/migration.js @@ -1,5 +1,3 @@ -const Cassandra = require('oae-util/lib/cassandra'); - /** * Four column families will be created: * @@ -14,13 +12,17 @@ const Cassandra = require('oae-util/lib/cassandra'); * - The column family AuthzMembershipsIndirectCache holds a cache of only groups that a principal * is strictly a member of indirectly. Meaning, if a user is a member both directly and indirectly, * the group will not be a part of indirect cache - * + */ + +import { createColumnFamilies } from 'oae-util/lib/cassandra'; + +/** * @param {Function} callback Standard callback function * @param {Object} callback.err An error that occurred, if any * @api private */ const ensureSchema = function(callback) { - Cassandra.createColumnFamilies( + createColumnFamilies( { // Deleted schema AuthzDeleted: 'CREATE TABLE "AuthzDeleted" ("resourceId" text PRIMARY KEY, "deleted" boolean)', @@ -49,4 +51,4 @@ const ensureSchema = function(callback) { ); }; -module.exports = { ensureSchema }; +export { ensureSchema }; diff --git a/packages/oae-authz/lib/model.js b/packages/oae-authz/lib/model.js index 54265a3dad..5c9d2e8589 100644 --- a/packages/oae-authz/lib/model.js +++ b/packages/oae-authz/lib/model.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; /** * A Principal object that represents a principal in the roles system. @@ -243,7 +243,7 @@ EmailChangeInfo.fromIdChangeInfo = function(idChangeInfo) { return new EmailChangeInfo(idChangeInfo.changes, idChangeInfo.roles, idChangeInfo.ids); }; -module.exports = { +export { Principal, Resource, ShareTarget, diff --git a/packages/oae-authz/lib/permissions.js b/packages/oae-authz/lib/permissions.js index 5be6fecc2a..a64b383a0d 100644 --- a/packages/oae-authz/lib/permissions.js +++ b/packages/oae-authz/lib/permissions.js @@ -13,17 +13,17 @@ * permissions and limitations under the License. */ -const AuthzAPI = require('oae-authz'); +import * as AuthzAPI from 'oae-authz'; -const _ = require('underscore'); +import _ from 'underscore'; -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzInvitationsUtil = require('oae-authz/lib/invitations/util'); -const AuthzModel = require('oae-authz/lib/model'); -const AuthzUtil = require('oae-authz/lib/util'); +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as AuthzInvitationsUtil from 'oae-authz/lib/invitations/util'; +import * as AuthzModel from 'oae-authz/lib/model'; +import * as AuthzUtil from 'oae-authz/lib/util'; -const TenantsAPI = require('oae-tenants'); -const TenantsUtil = require('oae-tenants/lib/util'); +import * as TenantsAPI from 'oae-tenants'; +import * as TenantsUtil from 'oae-tenants/lib/util'; /** * Determine which of all potential permissions a user has @@ -111,10 +111,12 @@ const canManage = function(ctx, resource, callback) { if (err) { return callback(err); } + if (implicitRole === AuthzConstants.role.MANAGER) { // We have an implicit manager role (e.g., we are an administrator), succeed return callback(); } + if (AuthzUtil.isUserId(resource.id)) { // It is not possible to have an explicit role on a user, short-circuit here return callback(permissionErr); @@ -125,6 +127,7 @@ const canManage = function(ctx, resource, callback) { if (err) { return callback(err); } + if (!hasRole) { return callback(permissionErr); } @@ -158,11 +161,13 @@ const canManageMessage = function(ctx, parentResource, message, callback) { if (err) { return callback(err); } + if (!permissions.canInteract) { // If the user cannot interact, they cannot manage the message even if they were the // author return callback(permissionErr); } + if (ctx.user().id !== message.createdBy && !permissions.canManage) { // The user cannot delete the message if they weren't the author and if they can't // manage the parent resource @@ -198,15 +203,18 @@ const canView = function(ctx, resource, callback) { if (err) { return callback(err); } + if (implicitRole) { // We have an implicit access, no reason to try and find an explicit access because we // can atleast view return callback(); } + if (!user) { // Anonymous user with no implicit access cannot view return callback(permissionErr); } + if (AuthzUtil.isUserId(resource.id)) { // Users can't have explicit access, therefore we can short-circuit here return callback(permissionErr); @@ -217,6 +225,7 @@ const canView = function(ctx, resource, callback) { if (err) { return callback(err); } + if (!hasAnyRole) { return callback(permissionErr); } @@ -240,6 +249,7 @@ const canEdit = function(ctx, resource, callback) { if (err) { return callback(err); } + if (!permissions.canEdit) { return callback({ code: 401, @@ -277,6 +287,7 @@ const canShare = function(ctx, resource, targets, role, callback) { if (err) { return callback(err); } + if (!permissions.canShare) { return callback({ code: 401, @@ -294,6 +305,7 @@ const canShare = function(ctx, resource, targets, role, callback) { if (err) { return callback(err); } + if (permissions.canManage) { // If we can manage the resource we don't need to check that the user in context can // extend the explicit access of the resource outside the set visibility @@ -374,6 +386,7 @@ const canJoin = function(ctx, resource, callback) { msg: 'The current user does not have access to join this resource' }); } + if (resource.joinable !== AuthzConstants.joinable.YES) { return callback({ code: 401, msg: 'The resource being joined is not joinable' }); } @@ -398,6 +411,7 @@ const canJoin = function(ctx, resource, callback) { if (err) { return callback(err); } + if (_.isEmpty(memberChangeInfo.members.added)) { return callback({ code: 400, @@ -437,6 +451,7 @@ const canRemoveRole = function(ctx, principal, resource, callback) { msg: 'The current user does not have access to remove this principal' }); } + if (err) { return callback(err); } @@ -452,12 +467,14 @@ const canRemoveRole = function(ctx, principal, resource, callback) { if (err) { return callback(err); } + if (_.isEmpty(memberChangeInfo.members.removed)) { return callback({ code: 400, msg: 'The principal being removed is not currently a member of the resource' }); } + if ( !_.chain(memberChangeInfo.roles.after) .values() @@ -508,6 +525,7 @@ const canSetRoles = function(ctx, resource, targetRoles, callback) { if (err) { return callback(err); } + if ( !_.isEmpty(memberChangeInfo.changes) && !_.chain(memberChangeInfo.roles.after) @@ -569,6 +587,7 @@ const canInteract = function(ctx, resources, callback) { if (!_.isArray(resources)) { return canInteract(ctx, _.compact([resources]), callback); } + if (_.isEmpty(resources)) { return callback(); } @@ -699,6 +718,7 @@ const _validateRoleChanges = function(ctx, resource, targetRoles, opts, callback // Contextualize the error a bit better than the generic `canInteract` error return callback(_.extend({ invalidPrincipals: err.invalidResources }, interactionErr)); } + if (err) { return callback(err); } @@ -738,12 +758,14 @@ const _canInteract = function(ctx, resource, callback) { if (err) { return callback(err); } + if (!canInteract) { if (!user) { // Anonymous users will not have an explicit role on anything, so we can // short-circuit return callback(permissionErr); } + if ((!resource.id && resource.email) || AuthzUtil.isUserId(resource.id)) { // If the target resource is a user (local or invited by email address) then we // cannot have an explicit role. So short-circuit @@ -761,6 +783,7 @@ const _canInteract = function(ctx, resource, callback) { if (err) { return callback(err); } + if (!hasAnyRole) { return callback(permissionErr); } @@ -856,7 +879,7 @@ const _emailToResource = function(email) { return result; }; -module.exports = { +export { resolveEffectivePermissions, canManage, canManageMessage, diff --git a/packages/oae-authz/lib/search.js b/packages/oae-authz/lib/search.js index 8b4270323c..c7db97d0d2 100644 --- a/packages/oae-authz/lib/search.js +++ b/packages/oae-authz/lib/search.js @@ -13,13 +13,15 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const AuthzAPI = require('oae-authz'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzUtil = require('oae-authz/lib/util'); -const SearchAPI = require('oae-search'); -const SearchUtil = require('oae-search/lib/util'); +import * as AuthzAPI from 'oae-authz'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as SearchAPI from 'oae-search'; +import * as SearchUtil from 'oae-search/lib/util'; +import * as resourceMembersSchema from './search/schema/resourceMembersSchema'; +import * as resourceMembershipsSchema from './search/schema/resourceMembershipsSchema'; /** * Initializes the child search documents for the Authz module @@ -30,7 +32,7 @@ const SearchUtil = require('oae-search/lib/util'); const init = function(callback) { const membersChildSearchDocumentOptions = { resourceTypes: ['content', 'discussion', 'group'], - schema: require('./search/schema/resourceMembersSchema'), + schema: resourceMembersSchema, producer(resources, callback) { return _produceResourceMembersDocuments(resources.slice(), callback); } @@ -38,7 +40,7 @@ const init = function(callback) { const membershipsChildSearchDocumentOptions = { resourceTypes: ['group', 'user'], - schema: require('./search/schema/resourceMembershipsSchema'), + schema: resourceMembershipsSchema, producer(resources, callback) { return _produceResourceMembershipsDocuments(resources.slice(), callback); } @@ -220,7 +222,4 @@ const _getMembershipIds = function(resource, callback) { ); }; -module.exports = { - init, - fireMembershipUpdateTasks -}; +export { init, fireMembershipUpdateTasks }; diff --git a/packages/oae-authz/lib/search/schema/resourceMembersSchema.js b/packages/oae-authz/lib/search/schema/resourceMembersSchema.js index 4e28906537..d9a10bd66c 100644 --- a/packages/oae-authz/lib/search/schema/resourceMembersSchema.js +++ b/packages/oae-authz/lib/search/schema/resourceMembersSchema.js @@ -23,11 +23,9 @@ * @return {Object} schema The resource members schema object * {String[]} schema.direct_members A multi-value field that holds the direct member ids of the parent resource */ -module.exports = { - // eslint-disable-next-line camelcase - direct_members: { - type: 'string', - store: 'no', - index: 'not_analyzed' - } +// eslint-disable-next-line camelcase +export const direct_members = { + type: 'string', + store: 'no', + index: 'not_analyzed' }; diff --git a/packages/oae-authz/lib/search/schema/resourceMembershipsSchema.js b/packages/oae-authz/lib/search/schema/resourceMembershipsSchema.js index dbfe965667..6d45f14c3a 100644 --- a/packages/oae-authz/lib/search/schema/resourceMembershipsSchema.js +++ b/packages/oae-authz/lib/search/schema/resourceMembershipsSchema.js @@ -24,11 +24,9 @@ * @return {Object} schema The resource memberships schema object * {String[]} schema.direct_memberships A multi-value field that holds the direct parent group ids to which the resource is a member */ -module.exports = { - // eslint-disable-next-line camelcase - direct_memberships: { - type: 'string', - store: 'no', - index: 'not_analyzed' - } +// eslint-disable-next-line camelcase +export const direct_memberships = { + type: 'string', + store: 'no', + index: 'not_analyzed' }; diff --git a/packages/oae-authz/lib/test/util.js b/packages/oae-authz/lib/test/util.js index 5640fe42fd..efd5e71080 100644 --- a/packages/oae-authz/lib/test/util.js +++ b/packages/oae-authz/lib/test/util.js @@ -13,21 +13,21 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const url = require('url'); -const _ = require('underscore'); +import assert from 'assert'; +import url from 'url'; +import _ from 'underscore'; -const LibraryAPI = require('oae-library'); -const OaeUtil = require('oae-util/lib/util'); -const RestAPI = require('oae-rest'); -const SearchTestUtil = require('oae-search/lib/test/util'); +import * as LibraryAPI from 'oae-library'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as RestAPI from 'oae-rest'; +import * as SearchTestUtil from 'oae-search/lib/test/util'; -const AuthzAPI = require('oae-authz'); -const AuthzDelete = require('oae-authz/lib/delete'); -const AuthzInvitationsDAO = require('oae-authz/lib/invitations/dao'); -const AuthzUtil = require('oae-authz/lib/util'); +import * as AuthzAPI from 'oae-authz'; +import * as AuthzDelete from 'oae-authz/lib/delete'; +import * as AuthzInvitationsDAO from 'oae-authz/lib/invitations/dao'; +import * as AuthzUtil from 'oae-authz/lib/util'; -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; /** * Delete the given resource by its id and ensure it completes successfully @@ -146,30 +146,25 @@ const assertAcceptInvitationFails = function(restContext, token, httpCode, callb // Get invitations before attempting to accept, if applicable. Since this a failure scenario, it // is possible that the token is completely invalid OaeUtil.invokeIfNecessary(token, AuthzInvitationsDAO.getEmailByToken, token, (err, email) => { - OaeUtil.invokeIfNecessary( - email, - AuthzInvitationsDAO.getAllInvitationsByEmail, - email, - (err, invitationsBefore) => { - // Perform the accept - RestAPI.Invitations.acceptInvitation(restContext, token, err => { - assert.ok(err); - assert.strictEqual(err.code, httpCode); - - // Ensure we get the same result from querying invitations to ensure that failing to - // accept the invitation did not trash them - OaeUtil.invokeIfNecessary( - email, - AuthzInvitationsDAO.getAllInvitationsByEmail, - email, - (err, invitationsAfter) => { - assert.deepStrictEqual(invitationsBefore, invitationsAfter); - return callback(); - } - ); - }); - } - ); + OaeUtil.invokeIfNecessary(email, AuthzInvitationsDAO.getAllInvitationsByEmail, email, (err, invitationsBefore) => { + // Perform the accept + RestAPI.Invitations.acceptInvitation(restContext, token, err => { + assert.ok(err); + assert.strictEqual(err.code, httpCode); + + // Ensure we get the same result from querying invitations to ensure that failing to + // accept the invitation did not trash them + OaeUtil.invokeIfNecessary( + email, + AuthzInvitationsDAO.getAllInvitationsByEmail, + email, + (err, invitationsAfter) => { + assert.deepStrictEqual(invitationsBefore, invitationsAfter); + return callback(); + } + ); + }); + }); }); }; @@ -202,13 +197,7 @@ const assertGetInvitationsSucceeds = function(restContext, resourceType, resourc * @param {Invitation[]} callback.invitations The invitations that are pending for the resource * @throws {AssertionError} Thrown if any assertions fail */ -const assertGetInvitationsFails = function( - restContext, - resourceType, - resourceId, - httpCode, - callback -) { +const assertGetInvitationsFails = function(restContext, resourceType, resourceId, httpCode, callback) { RestAPI.Invitations.getInvitations(restContext, resourceType, resourceId, (err, result) => { assert.ok(err); assert.strictEqual(err.code, httpCode); @@ -226,13 +215,7 @@ const assertGetInvitationsFails = function( * @param {Function} callback Invoked when all assertions pass * @throws {AssertionError} Thrown if any assertions fail */ -const assertResendInvitationSucceeds = function( - restContext, - resourceType, - resourceId, - email, - callback -) { +const assertResendInvitationSucceeds = function(restContext, resourceType, resourceId, email, callback) { RestAPI.Invitations.resendInvitation(restContext, resourceType, resourceId, email, err => { assert.ok(!err); return callback(); @@ -251,14 +234,7 @@ const assertResendInvitationSucceeds = function( * @param {Function} callback Invoked when all assertions pass * @throws {AssertionError} Thrown if any assertions fail */ -const assertResendInvitationFails = function( - restContext, - resourceType, - resourceId, - email, - httpCode, - callback -) { +const assertResendInvitationFails = function(restContext, resourceType, resourceId, email, httpCode, callback) { RestAPI.Invitations.resendInvitation(restContext, resourceType, resourceId, email, err => { assert.ok(err); assert.strictEqual(err.code, httpCode); @@ -331,10 +307,7 @@ const assertAuthzMembersGraphIdsEqual = function(resourceIds, expectedIds, callb const assertPrincipalMembershipsGraphIdsEqual = function(principalId, expectedIds, callback) { AuthzAPI.getPrincipalMembershipsGraph(principalId, (err, graph) => { assert.ok(!err); - assert.deepStrictEqual( - _.pluck(graph.traverseOut(principalId), 'id').sort(), - expectedIds.slice().sort() - ); + assert.deepStrictEqual(_.pluck(graph.traverseOut(principalId), 'id').sort(), expectedIds.slice().sort()); return callback(graph); }); }; @@ -418,6 +391,7 @@ const assertCreateMembershipsGraphSucceeds = function(graph, callback, _ops) { return assertCreateMembershipsGraphSucceeds(graph, callback, _ops); } + if (_.isEmpty(_ops)) { return callback(); } @@ -482,9 +456,7 @@ const getEmailRolesFromResults = function(invitations) { * @throws {AssertionError} Thrown if there is no invitation url */ const parseInvitationUrlFromMessage = function(message) { - const match = message.html.match( - /href="(https?:\/\/[^/]+\/signup\?url=%2F%3FinvitationToken%3D[^"]+)"/ - ); + const match = message.html.match(/href="(https?:\/\/[^/]+\/signup\?url=%2F%3FinvitationToken%3D[^"]+)"/); assert.ok(match); assert.strictEqual(match.length, 2); @@ -525,7 +497,7 @@ const _toLowerCase = function(str) { return str.toLowerCase(); }; -module.exports = { +export { assertSetDeletedSucceeds, assertUnsetDeletedSucceeds, assertAcceptInvitationForEmailSucceeds, diff --git a/packages/oae-authz/lib/util.js b/packages/oae-authz/lib/util.js index eca0939ef3..77353e2bd4 100644 --- a/packages/oae-authz/lib/util.js +++ b/packages/oae-authz/lib/util.js @@ -13,11 +13,11 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzDelete = require('oae-authz/lib/delete'); -const AuthzModel = require('oae-authz/lib/model'); +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as AuthzDelete from 'oae-authz/lib/delete'; +import * as AuthzModel from 'oae-authz/lib/model'; /** * Construct a resource based on the given id. @@ -116,6 +116,7 @@ const parseShareTarget = function(shareTargetStr) { if (shareTargetSplit.length === 1) { return { email }; } + const userId = shareTargetSplit.slice(1).join(':'); if (isUserId(userId)) { return { email, principalId: userId }; @@ -311,7 +312,7 @@ const getAuthzId = function(resource) { return resource.groupId || resource.id; }; -module.exports = { +export { getResourceFromId, getPrincipalFromId, toId, diff --git a/packages/oae-authz/lib/validator.js b/packages/oae-authz/lib/validator.js index 7a9eb68c7d..27027adf8a 100644 --- a/packages/oae-authz/lib/validator.js +++ b/packages/oae-authz/lib/validator.js @@ -13,11 +13,11 @@ * permissions and limitations under the License. */ -const util = require('util'); +import util from 'util'; -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzUtil = require('oae-authz/lib/util'); -const { Validator } = require('oae-util/lib/validator'); +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import { Validator } from 'oae-util/lib/validator'; /** * Checks whether or not the string in context is a valid principal id @@ -120,10 +120,7 @@ Validator.prototype.isResource = function(resource) { */ Validator.prototype.isValidRole = function() { if (!AuthzUtil.isRole(this.str)) { - this.error( - this.msg || - util.format('A role must be one of: %s', AuthzConstants.role.ALL_PRIORITY.join(', ')) - ); + this.error(this.msg || util.format('A role must be one of: %s', AuthzConstants.role.ALL_PRIORITY.join(', '))); } }; @@ -142,10 +139,7 @@ Validator.prototype.isValidRoleChange = function() { if (this.str !== false && !AuthzUtil.isRole(this.str)) { this.error( this.msg || - util.format( - 'A role change must either be false, or one of: %s', - AuthzConstants.role.ALL_PRIORITY.join(', ') - ) + util.format('A role change must either be false, or one of: %s', AuthzConstants.role.ALL_PRIORITY.join(', ')) ); } }; @@ -170,4 +164,4 @@ Validator.prototype.isValidShareTarget = function() { } }; -module.exports = { Validator }; +export { Validator }; diff --git a/packages/oae-authz/tests/test-authzgraph.js b/packages/oae-authz/tests/test-authzgraph.js index 3df24c1367..54673c6a10 100644 --- a/packages/oae-authz/tests/test-authzgraph.js +++ b/packages/oae-authz/tests/test-authzgraph.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import AuthzGraph from 'oae-authz/lib/internal/graph'; -const AuthzGraph = require('oae-authz/lib/internal/graph'); +import _ from 'underscore'; describe('Authz Graph', () => { /** @@ -194,45 +194,15 @@ describe('Authz Graph', () => { graph.addEdge('e', 'i'); // Verify that the inbound and outbound traversals are depth first and does not repeat - assert.deepStrictEqual(_.pluck(graph.traverseIn('a'), 'id'), [ - 'a', - 'i', - 'h', - 'g', - 'f', - 'e', - 'd', - 'c', - 'b' - ]); - assert.deepStrictEqual(_.pluck(graph.traverseOut('a'), 'id'), [ - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]); + assert.deepStrictEqual(_.pluck(graph.traverseIn('a'), 'id'), ['a', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b']); + assert.deepStrictEqual(_.pluck(graph.traverseOut('a'), 'id'), ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']); // Verify that if we delete the circular graph edges from the vowels, it still finds // some different paths around graph.removeEdge('d', 'e'); graph.removeEdge('h', 'i'); assert.deepStrictEqual(_.pluck(graph.traverseIn('a'), 'id'), ['a', 'i', 'e']); - assert.deepStrictEqual(_.pluck(graph.traverseOut('a'), 'id'), [ - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]); + assert.deepStrictEqual(_.pluck(graph.traverseOut('a'), 'id'), ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']); // Delete 'e', ensure paths are broken pretty good graph.removeNode('e'); diff --git a/packages/oae-authz/tests/test-delete.js b/packages/oae-authz/tests/test-delete.js index 34a89db4f6..814ae1aaed 100644 --- a/packages/oae-authz/tests/test-delete.js +++ b/packages/oae-authz/tests/test-delete.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const AuthzTestUtil = require('oae-authz/lib/test/util'); +import * as AuthzTestUtil from 'oae-authz/lib/test/util'; describe('Authz Delete', () => { /** @@ -30,11 +30,7 @@ describe('Authz Delete', () => { // Verify we successfully delete the second id AuthzTestUtil.assertSetDeletedSucceeds(group2, () => { // Verify isDeleted that contains both deleted ids works as expected - return AuthzTestUtil.assertIsDeletedSucceeds( - [group1, group2], - [group1, group2], - callback - ); + return AuthzTestUtil.assertIsDeletedSucceeds([group1, group2], [group1, group2], callback); }); }); }); diff --git a/packages/oae-authz/tests/test-groups.js b/packages/oae-authz/tests/test-groups.js index ce3200dc0a..21be8cf4df 100644 --- a/packages/oae-authz/tests/test-groups.js +++ b/packages/oae-authz/tests/test-groups.js @@ -13,16 +13,18 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); -const shortid = require('shortid'); +import assert from 'assert'; +import AuthzGraph from 'oae-authz/lib/internal/graph'; -const TestsUtil = require('oae-tests/lib/util'); +import _ from 'underscore'; +import shortid from 'shortid'; -const AuthzAPI = require('oae-authz'); -const AuthzGraph = require('oae-authz/lib/internal/graph'); -const AuthzTestUtil = require('oae-authz/lib/test/util'); -const AuthzUtil = require('oae-authz/lib/util'); +import * as TestsUtil from 'oae-tests/lib/util'; +import * as AuthzAPI from 'oae-authz'; + +import * as AuthzTestUtil from 'oae-authz/lib/test/util'; + +import * as AuthzUtil from 'oae-authz/lib/util'; describe('Authz Groups', () => { /** @@ -123,81 +125,57 @@ describe('Authz Groups', () => { describe('Add group member', () => { it('verify invalid group id error', callback => { - AuthzAPI.updateRoles( - 'not a valid id', - _.oaeObj('u:cam:mrvisser', 'member'), - (err, usersInvalidated) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.ok(!usersInvalidated); - return callback(); - } - ); + AuthzAPI.updateRoles('not a valid id', _.oaeObj('u:cam:mrvisser', 'member'), (err, usersInvalidated) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.ok(!usersInvalidated); + return callback(); + }); }); it('verify non-group group id error', callback => { - AuthzAPI.updateRoles( - 'u:cam:mrvisser', - _.oaeObj('u:cam:mrvisser', 'member'), - (err, usersInvalidated) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.ok(!usersInvalidated); - return callback(); - } - ); + AuthzAPI.updateRoles('u:cam:mrvisser', _.oaeObj('u:cam:mrvisser', 'member'), (err, usersInvalidated) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.ok(!usersInvalidated); + return callback(); + }); }); it('verify invalid member id error', callback => { - AuthzAPI.updateRoles( - 'g:oae:oae-team', - _.oaeObj('not a valid id', 'member'), - (err, usersInvalidated) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.ok(!usersInvalidated); - return callback(); - } - ); + AuthzAPI.updateRoles('g:oae:oae-team', _.oaeObj('not a valid id', 'member'), (err, usersInvalidated) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.ok(!usersInvalidated); + return callback(); + }); }); it('verify non-principal member id error', callback => { - AuthzAPI.updateRoles( - 'g:oae:oae-team', - _.oaeObj('c:content:id', 'member'), - (err, usersInvalidated) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.ok(!usersInvalidated); - return callback(); - } - ); + AuthzAPI.updateRoles('g:oae:oae-team', _.oaeObj('c:content:id', 'member'), (err, usersInvalidated) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.ok(!usersInvalidated); + return callback(); + }); }); it('verify null role error', callback => { - AuthzAPI.updateRoles( - 'g:oae:oae-team', - _.oaeObj('u:cam:mrvisser', null), - (err, usersInvalidated) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.ok(!usersInvalidated); - return callback(); - } - ); + AuthzAPI.updateRoles('g:oae:oae-team', _.oaeObj('u:cam:mrvisser', null), (err, usersInvalidated) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.ok(!usersInvalidated); + return callback(); + }); }); it('verify undefined role error', callback => { - AuthzAPI.updateRoles( - 'g:oae:oae-team', - _.oaeObj('u:cam:mrvisser', undefined), - (err, usersInvalidated) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.ok(!usersInvalidated); - return callback(); - } - ); + AuthzAPI.updateRoles('g:oae:oae-team', _.oaeObj('u:cam:mrvisser', undefined), (err, usersInvalidated) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.ok(!usersInvalidated); + return callback(); + }); }); it('verify user gets added to group', callback => { @@ -263,56 +241,40 @@ describe('Authz Groups', () => { describe('Remove group member', () => { it('verify invalid group id error', callback => { - AuthzAPI.updateRoles( - 'not a valid id', - _.oaeObj('u:cam:mrvisser', false), - (err, usersInvalidated) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.ok(!usersInvalidated); - assert.ok(!usersInvalidated); - return callback(); - } - ); + AuthzAPI.updateRoles('not a valid id', _.oaeObj('u:cam:mrvisser', false), (err, usersInvalidated) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.ok(!usersInvalidated); + assert.ok(!usersInvalidated); + return callback(); + }); }); it('verify non-group group id error', callback => { - AuthzAPI.updateRoles( - 'u:cam:mrvisser', - _.oaeObj('u:cam:mrvisser', false), - (err, usersInvalidated) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.ok(!usersInvalidated); - return callback(); - } - ); + AuthzAPI.updateRoles('u:cam:mrvisser', _.oaeObj('u:cam:mrvisser', false), (err, usersInvalidated) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.ok(!usersInvalidated); + return callback(); + }); }); it('verify invalid member id error', callback => { - AuthzAPI.updateRoles( - 'g:oae:oae-team', - _.oaeObj('not a valid id', false), - (err, usersInvalidated) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.ok(!usersInvalidated); - return callback(); - } - ); + AuthzAPI.updateRoles('g:oae:oae-team', _.oaeObj('not a valid id', false), (err, usersInvalidated) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.ok(!usersInvalidated); + return callback(); + }); }); it('verify non-principal member id error', callback => { - AuthzAPI.updateRoles( - 'g:oae:oae-team', - _.oaeObj('c:content:id', false), - (err, usersInvalidated) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.ok(!usersInvalidated); - return callback(); - } - ); + AuthzAPI.updateRoles('g:oae:oae-team', _.oaeObj('c:content:id', false), (err, usersInvalidated) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.ok(!usersInvalidated); + return callback(); + }); }); it('verify user gets removed from group', callback => { @@ -366,94 +328,66 @@ describe('Authz Groups', () => { describe('#updateAuthzGroupMembers()', () => { it('verify invalid group id error', callback => { - AuthzAPI.updateRoles( - 'not a valid id', - { 'u:cam:mrvisser': 'member' }, - (err, usersInvalidated) => { - assert.ok(err); - assert.ok(!usersInvalidated); - assert.strictEqual(err.code, 400); - return callback(); - } - ); + AuthzAPI.updateRoles('not a valid id', { 'u:cam:mrvisser': 'member' }, (err, usersInvalidated) => { + assert.ok(err); + assert.ok(!usersInvalidated); + assert.strictEqual(err.code, 400); + return callback(); + }); }); it('verify non-group group id error', callback => { - AuthzAPI.updateRoles( - 'u:cam:mrvisser', - { 'u:cam:mrvisser': 'member' }, - (err, usersInvalidated) => { - assert.ok(err); - assert.ok(!usersInvalidated); - assert.strictEqual(err.code, 400); - return callback(); - } - ); + AuthzAPI.updateRoles('u:cam:mrvisser', { 'u:cam:mrvisser': 'member' }, (err, usersInvalidated) => { + assert.ok(err); + assert.ok(!usersInvalidated); + assert.strictEqual(err.code, 400); + return callback(); + }); }); it('verify invalid member id error', callback => { - AuthzAPI.updateRoles( - 'g:oae:oae-team', - { 'not a valid id': 'member' }, - (err, usersInvalidated) => { - assert.ok(err); - assert.ok(!usersInvalidated); - assert.strictEqual(err.code, 400); - return callback(); - } - ); + AuthzAPI.updateRoles('g:oae:oae-team', { 'not a valid id': 'member' }, (err, usersInvalidated) => { + assert.ok(err); + assert.ok(!usersInvalidated); + assert.strictEqual(err.code, 400); + return callback(); + }); }); it('verify non-principal member id error', callback => { - AuthzAPI.updateRoles( - 'g:oae:oae-team', - { 'c:oae:mrvisser': 'member' }, - (err, usersInvalidated) => { - assert.ok(err); - assert.ok(!usersInvalidated); - assert.strictEqual(err.code, 400); - return callback(); - } - ); + AuthzAPI.updateRoles('g:oae:oae-team', { 'c:oae:mrvisser': 'member' }, (err, usersInvalidated) => { + assert.ok(err); + assert.ok(!usersInvalidated); + assert.strictEqual(err.code, 400); + return callback(); + }); }); it('verify null role error', callback => { - AuthzAPI.updateRoles( - 'g:oae:oae-team', - { 'u:cam:mrvisser': null }, - (err, usersInvalidated) => { - assert.ok(err); - assert.ok(!usersInvalidated); - assert.strictEqual(err.code, 400); - return callback(); - } - ); + AuthzAPI.updateRoles('g:oae:oae-team', { 'u:cam:mrvisser': null }, (err, usersInvalidated) => { + assert.ok(err); + assert.ok(!usersInvalidated); + assert.strictEqual(err.code, 400); + return callback(); + }); }); it('verify undefined role error', callback => { - AuthzAPI.updateRoles( - 'g:oae:oae-team', - { 'u:cam:mrvisser': undefined }, - (err, usersInvalidated) => { - assert.ok(err); - assert.ok(!usersInvalidated); - assert.strictEqual(err.code, 400); - return callback(); - } - ); + AuthzAPI.updateRoles('g:oae:oae-team', { 'u:cam:mrvisser': undefined }, (err, usersInvalidated) => { + assert.ok(err); + assert.ok(!usersInvalidated); + assert.strictEqual(err.code, 400); + return callback(); + }); }); it('verify blank role error', callback => { - AuthzAPI.updateRoles( - 'g:oae:oae-team', - { 'u:cam:mrvisser': ' ' }, - (err, usersInvalidated) => { - assert.ok(err); - assert.ok(!usersInvalidated); - assert.strictEqual(err.code, 400); - return callback(); - } - ); + AuthzAPI.updateRoles('g:oae:oae-team', { 'u:cam:mrvisser': ' ' }, (err, usersInvalidated) => { + assert.ok(err); + assert.ok(!usersInvalidated); + assert.strictEqual(err.code, 400); + return callback(); + }); }); it('verify general functionality', callback => { @@ -503,19 +437,9 @@ describe('Authz Groups', () => { verifyNoBidirectionalGroupMembership(groupId, mrvisserId, () => { verifyNoBidirectionalGroupMembership(groupId, nicoId, () => { - verifyBidirectionalGroupMembership( - groupId, - simongId, - 'manager', - () => { - verifyBidirectionalGroupMembership( - groupId, - bertId, - 'manager', - callback - ); - } - ); + verifyBidirectionalGroupMembership(groupId, simongId, 'manager', () => { + verifyBidirectionalGroupMembership(groupId, bertId, 'manager', callback); + }); }); }); }); @@ -603,23 +527,12 @@ describe('Authz Groups', () => { * @param {String} callback.nextToken The value to provide in the `start` parameter to get the next set of results * @throws {Error} An assertion error is thrown if an error occurs or the retrieved groups don't match the expected groups */ - const assertIndirectMemberships = function( - principalId, - start, - limit, - expectedGroups, - callback - ) { - AuthzAPI.getIndirectPrincipalMemberships( - principalId, - start, - limit, - (err, groupIds, nextToken) => { - assert.ok(!err); - assert.deepStrictEqual(groupIds, expectedGroups); - return callback(groupIds, nextToken); - } - ); + const assertIndirectMemberships = function(principalId, start, limit, expectedGroups, callback) { + AuthzAPI.getIndirectPrincipalMemberships(principalId, start, limit, (err, groupIds, nextToken) => { + assert.ok(!err); + assert.deepStrictEqual(groupIds, expectedGroups); + return callback(groupIds, nextToken); + }); }; /** @@ -631,19 +544,13 @@ describe('Authz Groups', () => { // eslint-disable-next-line no-unused-vars _setupMembershipChain((userId, groupId1, groupId2, groupId3, groupId4, groupId5) => { // Get 3 memberships for our user for the first time, ensuring we get the first 3 lexical groups - assertIndirectMemberships( - userId, - null, - 3, - [groupId1, groupId2, groupId3], - (groupIds, nextToken) => { - // Ensure subsequent request gets the last indirect group membership - // eslint-disable-next-line no-unused-vars - assertIndirectMemberships(userId, nextToken, 3, [groupId4], (groupIds, nextToken) => { - return callback(); - }); - } - ); + assertIndirectMemberships(userId, null, 3, [groupId1, groupId2, groupId3], (groupIds, nextToken) => { + // Ensure subsequent request gets the last indirect group membership + // eslint-disable-next-line no-unused-vars + assertIndirectMemberships(userId, nextToken, 3, [groupId4], (groupIds, nextToken) => { + return callback(); + }); + }); }); }); @@ -669,68 +576,60 @@ describe('Authz Groups', () => { (groupIds, nextToken) => { // Remove group4 as a member of group3. This will leave the user with // only 1 indirect group membership (group4) and group5 with 0 indirect memberships - AuthzAPI.updateRoles( - groupId3, - _.oaeObj(groupId4, false), - (err, usersInvalidated) => { - assert.ok(!err); - assert.strictEqual(usersInvalidated.length, 1); - assert.strictEqual(usersInvalidated[0], userId); + AuthzAPI.updateRoles(groupId3, _.oaeObj(groupId4, false), (err, usersInvalidated) => { + assert.ok(!err); + assert.strictEqual(usersInvalidated.length, 1); + assert.strictEqual(usersInvalidated[0], userId); - // Get all the indirect memberships for the user, he should only be an indirect member of group4 - assertIndirectMemberships( - userId, - null, - null, - [groupId4], - // eslint-disable-next-line no-unused-vars - (groupIds, nextToken) => { - // Get all the indirect memberships for group5, it should have no indirect memberships - assertIndirectMemberships( - groupId5, - null, - null, - [], - // eslint-disable-next-line no-unused-vars - (groupIds, nextToken) => { - // Restore the link between group3 and group4 - AuthzAPI.updateRoles( - groupId3, - _.oaeObj(groupId4, 'member'), - (err, usersInvalidated) => { - assert.ok(!err); - assert.strictEqual(usersInvalidated.length, 1); - assert.strictEqual(usersInvalidated[0], userId); - - // Get all the indirect memberships for the user, he should be an indirect member of all 4 groups again + // Get all the indirect memberships for the user, he should only be an indirect member of group4 + assertIndirectMemberships( + userId, + null, + null, + [groupId4], + // eslint-disable-next-line no-unused-vars + (groupIds, nextToken) => { + // Get all the indirect memberships for group5, it should have no indirect memberships + assertIndirectMemberships( + groupId5, + null, + null, + [], + // eslint-disable-next-line no-unused-vars + (groupIds, nextToken) => { + // Restore the link between group3 and group4 + AuthzAPI.updateRoles(groupId3, _.oaeObj(groupId4, 'member'), (err, usersInvalidated) => { + assert.ok(!err); + assert.strictEqual(usersInvalidated.length, 1); + assert.strictEqual(usersInvalidated[0], userId); + + // Get all the indirect memberships for the user, he should be an indirect member of all 4 groups again + assertIndirectMemberships( + userId, + null, + null, + [groupId1, groupId2, groupId3, groupId4], + // eslint-disable-next-line no-unused-vars + (groupIds, nextToken) => { + // Get all the indrect memberships for group5, it should be an indirect member of group 1, 2 and 3 assertIndirectMemberships( - userId, + groupId5, null, null, - [groupId1, groupId2, groupId3, groupId4], + [groupId1, groupId2, groupId3], // eslint-disable-next-line no-unused-vars (groupIds, nextToken) => { - // Get all the indrect memberships for group5, it should be an indirect member of group 1, 2 and 3 - assertIndirectMemberships( - groupId5, - null, - null, - [groupId1, groupId2, groupId3], - // eslint-disable-next-line no-unused-vars - (groupIds, nextToken) => { - return callback(); - } - ); + return callback(); } ); } ); - } - ); - } - ); - } - ); + }); + } + ); + } + ); + }); } ); } @@ -792,17 +691,12 @@ describe('Authz Groups', () => { assert.strictEqual(groupIds.length, 0); // Get the indirect memberships cached - AuthzAPI.getIndirectPrincipalMemberships( - userId, - undefined, - undefined, - (err, groupIds) => { - assert.ok(!err); - assert.ok(groupIds); - assert.strictEqual(groupIds.length, 0); - return callback(); - } - ); + AuthzAPI.getIndirectPrincipalMemberships(userId, undefined, undefined, (err, groupIds) => { + assert.ok(!err); + assert.ok(groupIds); + assert.strictEqual(groupIds.length, 0); + return callback(); + }); }); }); }); @@ -825,29 +719,19 @@ describe('Authz Groups', () => { assert.strictEqual(usersInvalidated[0], userId); // Get the memberships uncached - AuthzAPI.getIndirectPrincipalMemberships( - userId, - undefined, - undefined, - (err, groupIds) => { + AuthzAPI.getIndirectPrincipalMemberships(userId, undefined, undefined, (err, groupIds) => { + assert.ok(!err); + assert.ok(groupIds); + assert.strictEqual(groupIds.length, 0); + + // Get the memberships cached + AuthzAPI.getIndirectPrincipalMemberships(userId, undefined, undefined, (err, groupIds) => { assert.ok(!err); assert.ok(groupIds); assert.strictEqual(groupIds.length, 0); - - // Get the memberships cached - AuthzAPI.getIndirectPrincipalMemberships( - userId, - undefined, - undefined, - (err, groupIds) => { - assert.ok(!err); - assert.ok(groupIds); - assert.strictEqual(groupIds.length, 0); - return callback(); - } - ); - } - ); + return callback(); + }); + }); }); }); }); @@ -865,51 +749,33 @@ describe('Authz Groups', () => { assert.strictEqual(usersInvalidated.length, 1); assert.strictEqual(usersInvalidated[0], userId); - AuthzAPI.updateRoles( - groupId1, - _.oaeObj(nonMemberGroupId3, 'member'), - (err, usersInvalidated) => { + AuthzAPI.updateRoles(groupId1, _.oaeObj(nonMemberGroupId3, 'member'), (err, usersInvalidated) => { + assert.ok(!err); + assert.strictEqual(usersInvalidated.length, 0); + + AuthzAPI.updateRoles(groupId2, _.oaeObj(groupId1, 'member'), (err, usersInvalidated) => { assert.ok(!err); - assert.strictEqual(usersInvalidated.length, 0); + assert.strictEqual(usersInvalidated.length, 1); + assert.strictEqual(usersInvalidated[0], userId); - AuthzAPI.updateRoles( - groupId2, - _.oaeObj(groupId1, 'member'), - (err, usersInvalidated) => { + // Get the indirect memberships uncached + AuthzAPI.getIndirectPrincipalMemberships(userId, undefined, undefined, (err, groupIds) => { + assert.ok(!err); + assert.ok(groupIds); + assert.strictEqual(groupIds.length, 1); + assert.strictEqual(groupIds[0], groupId2); + + // Get the indirect memberships cached + AuthzAPI.getIndirectPrincipalMemberships(userId, undefined, undefined, (err, groupIds) => { assert.ok(!err); - assert.strictEqual(usersInvalidated.length, 1); - assert.strictEqual(usersInvalidated[0], userId); - - // Get the indirect memberships uncached - AuthzAPI.getIndirectPrincipalMemberships( - userId, - undefined, - undefined, - (err, groupIds) => { - assert.ok(!err); - assert.ok(groupIds); - assert.strictEqual(groupIds.length, 1); - assert.strictEqual(groupIds[0], groupId2); - - // Get the indirect memberships cached - AuthzAPI.getIndirectPrincipalMemberships( - userId, - undefined, - undefined, - (err, groupIds) => { - assert.ok(!err); - assert.ok(groupIds); - assert.strictEqual(groupIds.length, 1); - assert.strictEqual(groupIds[0], groupId2); - return callback(); - } - ); - } - ); - } - ); - } - ); + assert.ok(groupIds); + assert.strictEqual(groupIds.length, 1); + assert.strictEqual(groupIds[0], groupId2); + return callback(); + }); + }); + }); + }); }); }); @@ -934,48 +800,34 @@ describe('Authz Groups', () => { assert.ok(!err); assert.strictEqual(usersInvalidated.length, 1); assert.strictEqual(usersInvalidated[0], userId); - AuthzAPI.updateRoles( - groupId1, - _.oaeObj(groupId3, 'member'), - (err, usersInvalidated) => { + AuthzAPI.updateRoles(groupId1, _.oaeObj(groupId3, 'member'), (err, usersInvalidated) => { + assert.ok(!err); + assert.strictEqual(usersInvalidated.length, 1); + assert.strictEqual(usersInvalidated[0], userId); + + // Get the indirect memberships uncached + AuthzAPI.getIndirectPrincipalMemberships(userId, undefined, undefined, (err, groupIds) => { assert.ok(!err); - assert.strictEqual(usersInvalidated.length, 1); - assert.strictEqual(usersInvalidated[0], userId); - - // Get the indirect memberships uncached - AuthzAPI.getIndirectPrincipalMemberships( - userId, - undefined, - undefined, - (err, groupIds) => { - assert.ok(!err); - assert.ok(groupIds); - - // The indirect memberships does not contain groupId1 because, while the user is indirectly - // a member VIA circular hierarchy, they are actually directly a member, therefore it is - // not part of the strict indirect memberships list - assert.strictEqual(groupIds.length, 2); - assert.ok(_.contains(groupIds, groupId2)); - assert.ok(_.contains(groupIds, groupId3)); - - // Get the indirect memberships cached - AuthzAPI.getIndirectPrincipalMemberships( - userId, - undefined, - undefined, - (err, groupIds) => { - assert.ok(!err); - assert.ok(groupIds); - assert.strictEqual(groupIds.length, 2); - assert.ok(_.contains(groupIds, groupId2)); - assert.ok(_.contains(groupIds, groupId3)); - return callback(); - } - ); - } - ); - } - ); + assert.ok(groupIds); + + // The indirect memberships does not contain groupId1 because, while the user is indirectly + // a member VIA circular hierarchy, they are actually directly a member, therefore it is + // not part of the strict indirect memberships list + assert.strictEqual(groupIds.length, 2); + assert.ok(_.contains(groupIds, groupId2)); + assert.ok(_.contains(groupIds, groupId3)); + + // Get the indirect memberships cached + AuthzAPI.getIndirectPrincipalMemberships(userId, undefined, undefined, (err, groupIds) => { + assert.ok(!err); + assert.ok(groupIds); + assert.strictEqual(groupIds.length, 2); + assert.ok(_.contains(groupIds, groupId2)); + assert.ok(_.contains(groupIds, groupId3)); + return callback(); + }); + }); + }); }); }); }); @@ -1011,11 +863,17 @@ describe('Authz Groups', () => { assert.ok(!err); // Get the paged indirect memberships uncached - AuthzAPI.getIndirectPrincipalMemberships( - userId, - groupId3, - 3, - (err, groupIds, nextToken) => { + AuthzAPI.getIndirectPrincipalMemberships(userId, groupId3, 3, (err, groupIds, nextToken) => { + assert.ok(!err); + assert.ok(groupIds); + assert.strictEqual(groupIds.length, 3); + assert.ok(_.contains(groupIds, groupId4)); + assert.ok(_.contains(groupIds, groupId5)); + assert.ok(_.contains(groupIds, groupId6)); + assert.strictEqual(nextToken, groupIds[2]); + + // Get the paged indirect memberships cached + AuthzAPI.getIndirectPrincipalMemberships(userId, groupId3, 3, (err, groupIds, nextToken) => { assert.ok(!err); assert.ok(groupIds); assert.strictEqual(groupIds.length, 3); @@ -1023,25 +881,9 @@ describe('Authz Groups', () => { assert.ok(_.contains(groupIds, groupId5)); assert.ok(_.contains(groupIds, groupId6)); assert.strictEqual(nextToken, groupIds[2]); - - // Get the paged indirect memberships cached - AuthzAPI.getIndirectPrincipalMemberships( - userId, - groupId3, - 3, - (err, groupIds, nextToken) => { - assert.ok(!err); - assert.ok(groupIds); - assert.strictEqual(groupIds.length, 3); - assert.ok(_.contains(groupIds, groupId4)); - assert.ok(_.contains(groupIds, groupId5)); - assert.ok(_.contains(groupIds, groupId6)); - assert.strictEqual(nextToken, groupIds[2]); - return callback(); - } - ); - } - ); + return callback(); + }); + }); }); }); }); @@ -1076,58 +918,42 @@ describe('Authz Groups', () => { it('verify cache invalidation', callback => { _setupMembershipChain((userId, groupId1, groupId2, groupId3, groupId4, groupId5) => { // Get the indirect memberships for our user and a group so they get cached - _assertAllIndirectMembershipsEquals( - userId, - [groupId1, groupId2, groupId3, groupId4], - () => { - _assertAllIndirectMembershipsEquals(groupId5, [groupId1, groupId2, groupId3], () => { - // Remove group4 as a member of group3. This will leave the user with only 1 - // indirect group membership (group4) and group5 with 0 indirect memberships - AuthzAPI.updateRoles(groupId3, _.oaeObj(groupId4, false), (err, usersInvalidated) => { - assert.ok(!err); - assert.strictEqual(usersInvalidated.length, 1); - assert.strictEqual(usersInvalidated[0], userId); - - // Get all the indirect memberships for the user, he should only be an - // indirect member of group4 - _assertAllIndirectMembershipsEquals(userId, [groupId4], () => { - // Get all the indirect memberships for group5, it should have no - // indirect memberships - _assertAllIndirectMembershipsEquals(groupId5, [], () => { - // Restore the link between group3 and group4 - AuthzAPI.updateRoles( - groupId3, - _.oaeObj(groupId4, 'member'), - (err, usersInvalidated) => { - assert.ok(!err); - assert.strictEqual(usersInvalidated.length, 1); - assert.strictEqual(usersInvalidated[0], userId); - - // Get all the indirect memberships for the user, he should - // be an indirect member of all 4 groups again - _assertAllIndirectMembershipsEquals( - userId, - [groupId1, groupId2, groupId3, groupId4], - () => { - // Get all the indrect memberships for group5, it should - // be an indirect member of group 1, 2 and 3 - _assertAllIndirectMembershipsEquals( - groupId5, - [groupId1, groupId2, groupId3], - () => { - return callback(); - } - ); - } - ); - } - ); + _assertAllIndirectMembershipsEquals(userId, [groupId1, groupId2, groupId3, groupId4], () => { + _assertAllIndirectMembershipsEquals(groupId5, [groupId1, groupId2, groupId3], () => { + // Remove group4 as a member of group3. This will leave the user with only 1 + // indirect group membership (group4) and group5 with 0 indirect memberships + AuthzAPI.updateRoles(groupId3, _.oaeObj(groupId4, false), (err, usersInvalidated) => { + assert.ok(!err); + assert.strictEqual(usersInvalidated.length, 1); + assert.strictEqual(usersInvalidated[0], userId); + + // Get all the indirect memberships for the user, he should only be an + // indirect member of group4 + _assertAllIndirectMembershipsEquals(userId, [groupId4], () => { + // Get all the indirect memberships for group5, it should have no + // indirect memberships + _assertAllIndirectMembershipsEquals(groupId5, [], () => { + // Restore the link between group3 and group4 + AuthzAPI.updateRoles(groupId3, _.oaeObj(groupId4, 'member'), (err, usersInvalidated) => { + assert.ok(!err); + assert.strictEqual(usersInvalidated.length, 1); + assert.strictEqual(usersInvalidated[0], userId); + + // Get all the indirect memberships for the user, he should + // be an indirect member of all 4 groups again + _assertAllIndirectMembershipsEquals(userId, [groupId1, groupId2, groupId3, groupId4], () => { + // Get all the indrect memberships for group5, it should + // be an indirect member of group 1, 2 and 3 + _assertAllIndirectMembershipsEquals(groupId5, [groupId1, groupId2, groupId3], () => { + return callback(); + }); + }); }); }); }); }); - } - ); + }); + }); }); }); @@ -1243,41 +1069,33 @@ describe('Authz Groups', () => { assert.strictEqual(usersInvalidated.length, 1); assert.strictEqual(usersInvalidated[0], userId); - AuthzAPI.updateRoles( - groupId1, - _.oaeObj(nonMemberGroupId3, 'member'), - (err, usersInvalidated) => { - assert.ok(!err); - assert.strictEqual(usersInvalidated.length, 0); + AuthzAPI.updateRoles(groupId1, _.oaeObj(nonMemberGroupId3, 'member'), (err, usersInvalidated) => { + assert.ok(!err); + assert.strictEqual(usersInvalidated.length, 0); - AuthzAPI.updateRoles( - groupId2, - _.oaeObj(groupId1, 'member'), - (err, usersInvalidated) => { - assert.ok(!err); - assert.strictEqual(usersInvalidated.length, 1); - assert.strictEqual(usersInvalidated[0], userId); + AuthzAPI.updateRoles(groupId2, _.oaeObj(groupId1, 'member'), (err, usersInvalidated) => { + assert.ok(!err); + assert.strictEqual(usersInvalidated.length, 1); + assert.strictEqual(usersInvalidated[0], userId); - // Get the indirect memberships uncached - AuthzAPI.getAllIndirectPrincipalMemberships(userId, (err, groupIds) => { - assert.ok(!err); - assert.ok(groupIds); - assert.strictEqual(groupIds.length, 1); - assert.strictEqual(groupIds[0], groupId2); + // Get the indirect memberships uncached + AuthzAPI.getAllIndirectPrincipalMemberships(userId, (err, groupIds) => { + assert.ok(!err); + assert.ok(groupIds); + assert.strictEqual(groupIds.length, 1); + assert.strictEqual(groupIds[0], groupId2); - // Get the indirect memberships cached - AuthzAPI.getAllIndirectPrincipalMemberships(userId, (err, groupIds) => { - assert.ok(!err); - assert.ok(groupIds); - assert.strictEqual(groupIds.length, 1); - assert.strictEqual(groupIds[0], groupId2); - return callback(); - }); - }); - } - ); - } - ); + // Get the indirect memberships cached + AuthzAPI.getAllIndirectPrincipalMemberships(userId, (err, groupIds) => { + assert.ok(!err); + assert.ok(groupIds); + assert.strictEqual(groupIds.length, 1); + assert.strictEqual(groupIds[0], groupId2); + return callback(); + }); + }); + }); + }); }); }); @@ -1302,38 +1120,34 @@ describe('Authz Groups', () => { assert.ok(!err); assert.strictEqual(usersInvalidated.length, 1); assert.strictEqual(usersInvalidated[0], userId); - AuthzAPI.updateRoles( - groupId1, - _.oaeObj(groupId3, 'member'), - (err, usersInvalidated) => { + AuthzAPI.updateRoles(groupId1, _.oaeObj(groupId3, 'member'), (err, usersInvalidated) => { + assert.ok(!err); + assert.strictEqual(usersInvalidated.length, 1); + assert.strictEqual(usersInvalidated[0], userId); + + // Get the indirect memberships uncached + AuthzAPI.getAllIndirectPrincipalMemberships(userId, (err, groupIds) => { assert.ok(!err); - assert.strictEqual(usersInvalidated.length, 1); - assert.strictEqual(usersInvalidated[0], userId); + assert.ok(groupIds); + + // The indirect memberships does not contain groupId1 because, while the user is indirectly + // a member VIA circular hierarchy, they are actually directly a member, therefore it is + // not part of the strict indirect memberships list + assert.strictEqual(groupIds.length, 2); + assert.ok(_.contains(groupIds, groupId2)); + assert.ok(_.contains(groupIds, groupId3)); - // Get the indirect memberships uncached + // Get the indirect memberships cached AuthzAPI.getAllIndirectPrincipalMemberships(userId, (err, groupIds) => { assert.ok(!err); assert.ok(groupIds); - - // The indirect memberships does not contain groupId1 because, while the user is indirectly - // a member VIA circular hierarchy, they are actually directly a member, therefore it is - // not part of the strict indirect memberships list assert.strictEqual(groupIds.length, 2); assert.ok(_.contains(groupIds, groupId2)); assert.ok(_.contains(groupIds, groupId3)); - - // Get the indirect memberships cached - AuthzAPI.getAllIndirectPrincipalMemberships(userId, (err, groupIds) => { - assert.ok(!err); - assert.ok(groupIds); - assert.strictEqual(groupIds.length, 2); - assert.ok(_.contains(groupIds, groupId2)); - assert.ok(_.contains(groupIds, groupId3)); - return callback(); - }); + return callback(); }); - } - ); + }); + }); }); }); }); @@ -1784,11 +1598,17 @@ describe('Authz Groups', () => { assert.ok(!err); // Get the paged memberships uncached - AuthzAPI.getPrincipalMemberships( - userId, - groupId3, - 3, - (err, groupIds, nextToken) => { + AuthzAPI.getPrincipalMemberships(userId, groupId3, 3, (err, groupIds, nextToken) => { + assert.ok(!err); + assert.ok(groupIds); + assert.strictEqual(groupIds.length, 3); + assert.ok(_.contains(groupIds, groupId4)); + assert.ok(_.contains(groupIds, groupId5)); + assert.ok(_.contains(groupIds, groupId6)); + assert.strictEqual(nextToken, groupIds[2]); + + // Get the paged memberships cached + AuthzAPI.getPrincipalMemberships(userId, groupId3, 3, (err, groupIds, nextToken) => { assert.ok(!err); assert.ok(groupIds); assert.strictEqual(groupIds.length, 3); @@ -1796,25 +1616,9 @@ describe('Authz Groups', () => { assert.ok(_.contains(groupIds, groupId5)); assert.ok(_.contains(groupIds, groupId6)); assert.strictEqual(nextToken, groupIds[2]); - - // Get the paged memberships cached - AuthzAPI.getPrincipalMemberships( - userId, - groupId3, - 3, - (err, groupIds, nextToken) => { - assert.ok(!err); - assert.ok(groupIds); - assert.strictEqual(groupIds.length, 3); - assert.ok(_.contains(groupIds, groupId4)); - assert.ok(_.contains(groupIds, groupId5)); - assert.ok(_.contains(groupIds, groupId6)); - assert.strictEqual(nextToken, groupIds[2]); - callback(); - } - ); - } - ); + callback(); + }); + }); }); }); }); @@ -1981,100 +1785,66 @@ describe('Authz Groups', () => { // Verify adding a principal by adding bert as a member const bert = AuthzUtil.toId('u', 'cmac', 'bert'); const addBertChange = _.oaeObj(bert, 'member'); - AuthzAPI.computeMemberRolesAfterChanges( - groupId, - addBertChange, - null, - (err, idChangeInfo) => { + AuthzAPI.computeMemberRolesAfterChanges(groupId, addBertChange, null, (err, idChangeInfo) => { + assert.ok(!err); + assert.deepStrictEqual(idChangeInfo.changes, addBertChange); + assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); + assert.deepStrictEqual(idChangeInfo.roles.after, _.extend({}, rolesBefore, addBertChange)); + assert.deepStrictEqual(idChangeInfo.ids.added, [bert]); + assert.deepStrictEqual(idChangeInfo.ids.updated, []); + assert.deepStrictEqual(idChangeInfo.ids.removed, []); + + // Verify a role change by making mrvisser a manager + const mrvisserManagerChange = _.oaeObj(mrvisser, 'manager'); + AuthzAPI.computeMemberRolesAfterChanges(groupId, mrvisserManagerChange, null, (err, idChangeInfo) => { assert.ok(!err); - assert.deepStrictEqual(idChangeInfo.changes, addBertChange); + assert.deepStrictEqual(idChangeInfo.changes, mrvisserManagerChange); assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); - assert.deepStrictEqual( - idChangeInfo.roles.after, - _.extend({}, rolesBefore, addBertChange) - ); - assert.deepStrictEqual(idChangeInfo.ids.added, [bert]); - assert.deepStrictEqual(idChangeInfo.ids.updated, []); + assert.deepStrictEqual(idChangeInfo.roles.after, _.extend({}, rolesBefore, mrvisserManagerChange)); + assert.deepStrictEqual(idChangeInfo.ids.added, []); + assert.deepStrictEqual(idChangeInfo.ids.updated, [mrvisser]); assert.deepStrictEqual(idChangeInfo.ids.removed, []); - // Verify a role change by making mrvisser a manager - const mrvisserManagerChange = _.oaeObj(mrvisser, 'manager'); - AuthzAPI.computeMemberRolesAfterChanges( - groupId, - mrvisserManagerChange, - null, - (err, idChangeInfo) => { + // Verify a non-update by making simong a member (he already is a member) + const simonMemberChange = _.oaeObj(simong, 'member'); + AuthzAPI.computeMemberRolesAfterChanges(groupId, simonMemberChange, null, (err, idChangeInfo) => { + assert.ok(!err); + assert.deepStrictEqual(idChangeInfo.changes, {}); + assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); + assert.deepStrictEqual(idChangeInfo.roles.after, rolesBefore); + assert.deepStrictEqual(idChangeInfo.ids.added, []); + assert.deepStrictEqual(idChangeInfo.ids.updated, []); + assert.deepStrictEqual(idChangeInfo.ids.removed, []); + + // Verify removing a principal by removing simong + const simonRemoveChange = _.oaeObj(simong, false); + AuthzAPI.computeMemberRolesAfterChanges(groupId, simonRemoveChange, null, (err, idChangeInfo) => { assert.ok(!err); - assert.deepStrictEqual(idChangeInfo.changes, mrvisserManagerChange); + assert.deepStrictEqual(idChangeInfo.changes, simonRemoveChange); assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); - assert.deepStrictEqual( - idChangeInfo.roles.after, - _.extend({}, rolesBefore, mrvisserManagerChange) - ); + assert.deepStrictEqual(idChangeInfo.roles.after, _.omit(rolesBefore, simong)); assert.deepStrictEqual(idChangeInfo.ids.added, []); - assert.deepStrictEqual(idChangeInfo.ids.updated, [mrvisser]); - assert.deepStrictEqual(idChangeInfo.ids.removed, []); - - // Verify a non-update by making simong a member (he already is a member) - const simonMemberChange = _.oaeObj(simong, 'member'); - AuthzAPI.computeMemberRolesAfterChanges( - groupId, - simonMemberChange, - null, - (err, idChangeInfo) => { - assert.ok(!err); - assert.deepStrictEqual(idChangeInfo.changes, {}); - assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); - assert.deepStrictEqual(idChangeInfo.roles.after, rolesBefore); - assert.deepStrictEqual(idChangeInfo.ids.added, []); - assert.deepStrictEqual(idChangeInfo.ids.updated, []); - assert.deepStrictEqual(idChangeInfo.ids.removed, []); - - // Verify removing a principal by removing simong - const simonRemoveChange = _.oaeObj(simong, false); - AuthzAPI.computeMemberRolesAfterChanges( - groupId, - simonRemoveChange, - null, - (err, idChangeInfo) => { - assert.ok(!err); - assert.deepStrictEqual(idChangeInfo.changes, simonRemoveChange); - assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); - assert.deepStrictEqual( - idChangeInfo.roles.after, - _.omit(rolesBefore, simong) - ); - assert.deepStrictEqual(idChangeInfo.ids.added, []); - assert.deepStrictEqual(idChangeInfo.ids.updated, []); - assert.deepStrictEqual(idChangeInfo.ids.removed, [simong]); - - // Trying to remove the membership for a principal that has no permission should result in no change. - const unknownUser = AuthzUtil.toId('u', 'cmac', 'unknown'); - const unknownUserRemoveChange = _.oaeObj(unknownUser, false); - AuthzAPI.computeMemberRolesAfterChanges( - groupId, - unknownUserRemoveChange, - null, - (err, idChangeInfo) => { - assert.ok(!err); - assert.deepStrictEqual(idChangeInfo.changes, {}); - assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); - assert.deepStrictEqual(idChangeInfo.roles.after, rolesBefore); - assert.deepStrictEqual(idChangeInfo.ids.added, []); - assert.deepStrictEqual(idChangeInfo.ids.updated, []); - assert.deepStrictEqual(idChangeInfo.ids.removed, []); - - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + assert.deepStrictEqual(idChangeInfo.ids.updated, []); + assert.deepStrictEqual(idChangeInfo.ids.removed, [simong]); + + // Trying to remove the membership for a principal that has no permission should result in no change. + const unknownUser = AuthzUtil.toId('u', 'cmac', 'unknown'); + const unknownUserRemoveChange = _.oaeObj(unknownUser, false); + AuthzAPI.computeMemberRolesAfterChanges(groupId, unknownUserRemoveChange, null, (err, idChangeInfo) => { + assert.ok(!err); + assert.deepStrictEqual(idChangeInfo.changes, {}); + assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); + assert.deepStrictEqual(idChangeInfo.roles.after, rolesBefore); + assert.deepStrictEqual(idChangeInfo.ids.added, []); + assert.deepStrictEqual(idChangeInfo.ids.updated, []); + assert.deepStrictEqual(idChangeInfo.ids.removed, []); + + return callback(); + }); + }); + }); + }); + }); }); }); @@ -2097,119 +1867,108 @@ describe('Authz Groups', () => { // Verify adding a principal by adding bert as a member const bert = AuthzUtil.toId('u', 'cmaca', 'bert'); const addBertChange = _.oaeObj(bert, 'member'); - AuthzAPI.computeMemberRolesAfterChanges( - groupId, - addBertChange, - { promoteOnly: true }, - (err, idChangeInfo) => { - assert.ok(!err); - assert.deepStrictEqual(idChangeInfo.changes, addBertChange); - assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); - assert.deepStrictEqual( - idChangeInfo.roles.after, - _.extend({}, rolesBefore, addBertChange) - ); - assert.deepStrictEqual(idChangeInfo.ids.added, [bert]); - assert.deepStrictEqual(idChangeInfo.ids.updated, []); - assert.deepStrictEqual(idChangeInfo.ids.removed, []); - - // Verify a role change by making mrvisser an editor. It should cause a change - // because mrvisser is currently a lowly member - const mrvisserEditorChange = _.oaeObj(mrvisser, 'editor'); - AuthzAPI.computeMemberRolesAfterChanges( - groupId, - mrvisserEditorChange, - { promoteOnly: true }, - (err, idChangeInfo) => { - assert.ok(!err); - assert.deepStrictEqual(idChangeInfo.changes, mrvisserEditorChange); - assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); - assert.deepStrictEqual( - idChangeInfo.roles.after, - _.extend({}, rolesBefore, mrvisserEditorChange) - ); - assert.deepStrictEqual(idChangeInfo.ids.added, []); - assert.deepStrictEqual(idChangeInfo.ids.updated, [mrvisser]); - assert.deepStrictEqual(idChangeInfo.ids.removed, []); - - // Verify a role change by demoting stephen to a viewer. It should not - // result in a change because stephen's editor role is superior - const stephenViewerChange = _.oaeObj(stephen, 'viewer'); - AuthzAPI.computeMemberRolesAfterChanges( - groupId, - stephenViewerChange, - { promoteOnly: true }, - (err, idChangeInfo) => { - assert.ok(!err); - assert.deepStrictEqual(idChangeInfo.changes, {}); - assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); - assert.deepStrictEqual(idChangeInfo.roles.after, rolesBefore); - assert.deepStrictEqual(idChangeInfo.ids.added, []); - assert.deepStrictEqual(idChangeInfo.ids.updated, []); - assert.deepStrictEqual(idChangeInfo.ids.removed, []); - - // Verify a non-update by making simong a member (he already is a member), - // should result in no change - const simonMemberChange = _.oaeObj(simong, 'member'); - AuthzAPI.computeMemberRolesAfterChanges( - groupId, - simonMemberChange, - { promoteOnly: true }, - (err, idChangeInfo) => { - assert.ok(!err); - assert.deepStrictEqual(idChangeInfo.changes, {}); - assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); - assert.deepStrictEqual(idChangeInfo.roles.after, rolesBefore); - assert.deepStrictEqual(idChangeInfo.ids.added, []); - assert.deepStrictEqual(idChangeInfo.ids.updated, []); - assert.deepStrictEqual(idChangeInfo.ids.removed, []); - - // Verify removing a principal by removing simong, should result in no - // change - const simonRemoveChange = _.oaeObj(simong, false); - AuthzAPI.computeMemberRolesAfterChanges( - groupId, - simonRemoveChange, - { promoteOnly: true }, - (err, idChangeInfo) => { - assert.ok(!err); - assert.deepStrictEqual(idChangeInfo.changes, {}); - assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); - assert.deepStrictEqual(idChangeInfo.roles.after, rolesBefore); - assert.deepStrictEqual(idChangeInfo.ids.added, []); - assert.deepStrictEqual(idChangeInfo.ids.updated, []); - assert.deepStrictEqual(idChangeInfo.ids.removed, []); - - // Trying to remove the membership for a principal that has no - // permission should result in no change - const unknownUser = AuthzUtil.toId('u', 'cmaca', 'unknown'); - const unknownUserRemoveChange = _.oaeObj(unknownUser, false); - AuthzAPI.computeMemberRolesAfterChanges( - groupId, - unknownUserRemoveChange, - { promoteOnly: true }, - (err, idChangeInfo) => { - assert.ok(!err); - assert.deepStrictEqual(idChangeInfo.changes, {}); - assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); - assert.deepStrictEqual(idChangeInfo.roles.after, rolesBefore); - assert.deepStrictEqual(idChangeInfo.ids.added, []); - assert.deepStrictEqual(idChangeInfo.ids.updated, []); - assert.deepStrictEqual(idChangeInfo.ids.removed, []); - - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + AuthzAPI.computeMemberRolesAfterChanges(groupId, addBertChange, { promoteOnly: true }, (err, idChangeInfo) => { + assert.ok(!err); + assert.deepStrictEqual(idChangeInfo.changes, addBertChange); + assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); + assert.deepStrictEqual(idChangeInfo.roles.after, _.extend({}, rolesBefore, addBertChange)); + assert.deepStrictEqual(idChangeInfo.ids.added, [bert]); + assert.deepStrictEqual(idChangeInfo.ids.updated, []); + assert.deepStrictEqual(idChangeInfo.ids.removed, []); + + // Verify a role change by making mrvisser an editor. It should cause a change + // because mrvisser is currently a lowly member + const mrvisserEditorChange = _.oaeObj(mrvisser, 'editor'); + AuthzAPI.computeMemberRolesAfterChanges( + groupId, + mrvisserEditorChange, + { promoteOnly: true }, + (err, idChangeInfo) => { + assert.ok(!err); + assert.deepStrictEqual(idChangeInfo.changes, mrvisserEditorChange); + assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); + assert.deepStrictEqual(idChangeInfo.roles.after, _.extend({}, rolesBefore, mrvisserEditorChange)); + assert.deepStrictEqual(idChangeInfo.ids.added, []); + assert.deepStrictEqual(idChangeInfo.ids.updated, [mrvisser]); + assert.deepStrictEqual(idChangeInfo.ids.removed, []); + + // Verify a role change by demoting stephen to a viewer. It should not + // result in a change because stephen's editor role is superior + const stephenViewerChange = _.oaeObj(stephen, 'viewer'); + AuthzAPI.computeMemberRolesAfterChanges( + groupId, + stephenViewerChange, + { promoteOnly: true }, + (err, idChangeInfo) => { + assert.ok(!err); + assert.deepStrictEqual(idChangeInfo.changes, {}); + assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); + assert.deepStrictEqual(idChangeInfo.roles.after, rolesBefore); + assert.deepStrictEqual(idChangeInfo.ids.added, []); + assert.deepStrictEqual(idChangeInfo.ids.updated, []); + assert.deepStrictEqual(idChangeInfo.ids.removed, []); + + // Verify a non-update by making simong a member (he already is a member), + // should result in no change + const simonMemberChange = _.oaeObj(simong, 'member'); + AuthzAPI.computeMemberRolesAfterChanges( + groupId, + simonMemberChange, + { promoteOnly: true }, + (err, idChangeInfo) => { + assert.ok(!err); + assert.deepStrictEqual(idChangeInfo.changes, {}); + assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); + assert.deepStrictEqual(idChangeInfo.roles.after, rolesBefore); + assert.deepStrictEqual(idChangeInfo.ids.added, []); + assert.deepStrictEqual(idChangeInfo.ids.updated, []); + assert.deepStrictEqual(idChangeInfo.ids.removed, []); + + // Verify removing a principal by removing simong, should result in no + // change + const simonRemoveChange = _.oaeObj(simong, false); + AuthzAPI.computeMemberRolesAfterChanges( + groupId, + simonRemoveChange, + { promoteOnly: true }, + (err, idChangeInfo) => { + assert.ok(!err); + assert.deepStrictEqual(idChangeInfo.changes, {}); + assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); + assert.deepStrictEqual(idChangeInfo.roles.after, rolesBefore); + assert.deepStrictEqual(idChangeInfo.ids.added, []); + assert.deepStrictEqual(idChangeInfo.ids.updated, []); + assert.deepStrictEqual(idChangeInfo.ids.removed, []); + + // Trying to remove the membership for a principal that has no + // permission should result in no change + const unknownUser = AuthzUtil.toId('u', 'cmaca', 'unknown'); + const unknownUserRemoveChange = _.oaeObj(unknownUser, false); + AuthzAPI.computeMemberRolesAfterChanges( + groupId, + unknownUserRemoveChange, + { promoteOnly: true }, + (err, idChangeInfo) => { + assert.ok(!err); + assert.deepStrictEqual(idChangeInfo.changes, {}); + assert.deepStrictEqual(idChangeInfo.roles.before, rolesBefore); + assert.deepStrictEqual(idChangeInfo.roles.after, rolesBefore); + assert.deepStrictEqual(idChangeInfo.ids.added, []); + assert.deepStrictEqual(idChangeInfo.ids.updated, []); + assert.deepStrictEqual(idChangeInfo.ids.removed, []); + + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + }); }); }); }); diff --git a/packages/oae-authz/tests/test-invitations.js b/packages/oae-authz/tests/test-invitations.js index c70bcc9923..66b51cccab 100644 --- a/packages/oae-authz/tests/test-invitations.js +++ b/packages/oae-authz/tests/test-invitations.js @@ -13,30 +13,31 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const url = require('url'); -const util = require('util'); -const clone = require('clone'); -const _ = require('underscore'); +import * as assert from 'assert'; +import fs from 'fs'; +import url from 'url'; +import util from 'util'; + +import * as ActivityTestUtil from 'oae-activity/lib/test/util'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as ContentTestUtil from 'oae-content/lib/test/util'; +import * as DiscussionsTestUtil from 'oae-discussions/lib/test/util'; +import * as EmailTestUtil from 'oae-email/lib/test/util'; +import * as FoldersTestUtil from 'oae-folders/lib/test/util'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import * as Sanitization from 'oae-util/lib/sanitization'; +import * as SearchTestUtil from 'oae-search/lib/test/util'; +import * as TenantsAPI from 'oae-tenants'; +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; +import * as TestsUtil from 'oae-tests'; +import * as UIAPI from 'oae-ui'; + +import * as AuthzInvitationsDAO from 'oae-authz/lib/invitations/dao'; +import * as AuthzTestUtil from 'oae-authz/lib/test/util'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import clone from 'clone'; -const ActivityTestUtil = require('oae-activity/lib/test/util'); -const ConfigTestUtil = require('oae-config/lib/test/util'); -const ContentTestUtil = require('oae-content/lib/test/util'); -const DiscussionsTestUtil = require('oae-discussions/lib/test/util'); -const EmailTestUtil = require('oae-email/lib/test/util'); -const FoldersTestUtil = require('oae-folders/lib/test/util'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); -const Sanitization = require('oae-util/lib/sanitization'); -const SearchTestUtil = require('oae-search/lib/test/util'); -const TenantsAPI = require('oae-tenants'); -const TenantsTestUtil = require('oae-tenants/lib/test/util'); -const TestsUtil = require('oae-tests'); -const UIAPI = require('oae-ui'); - -const AuthzInvitationsDAO = require('oae-authz/lib/invitations/dao'); -const AuthzTestUtil = require('oae-authz/lib/test/util'); -const AuthzUtil = require('oae-authz/lib/util'); +const _ = require('underscore'); describe('Invitations', () => { // Initialize some rest contexts for anonymous and admin users @@ -49,13 +50,13 @@ describe('Invitations', () => { }; /*! - * This is a wrapper of `_.partial` that allows you to specify an argless function as a - * parameter that will be invoked to derive the argument value on each invokation - * - * @param {Function} fn The function to curry - * @param {...Object} args The variable number of arguments to partially apply the function with - * @return {Function} The partially applied function, just like `_.partial`, except any partial arguments that were functions are invoked on-the-fly for each invocation - */ + * This is a wrapper of `_.partial` that allows you to specify an argless function as a + * parameter that will be invoked to derive the argument value on each invokation + * + * @param {Function} fn The function to curry + * @param {...Object} args The variable number of arguments to partially apply the function with + * @return {Function} The partially applied function, just like `_.partial`, except any partial arguments that were functions are invoked on-the-fly for each invocation + */ const _partialWithFns = function(firstFn, ...args) { const firstArgs = args; @@ -84,8 +85,8 @@ describe('Invitations', () => { }; /*! - * Build a library of common functions across different known resource types - */ + * Build a library of common functions across different known resource types + */ const resourceFns = { content: { createSucceeds: _partialWithFns( @@ -118,13 +119,7 @@ describe('Invitations', () => { setRolesSucceeds: ContentTestUtil.assertUpdateContentMembersSucceeds, setRolesFails: ContentTestUtil.assertUpdateContentMembersFails, getMembersSucceeds: _partialWithFns(ContentTestUtil.getAllContentMembers, _, _, null, _), - getLibrarySucceeds: _partialWithFns( - ContentTestUtil.assertGetAllContentLibrarySucceeds, - _, - _, - null, - _ - ), + getLibrarySucceeds: _partialWithFns(ContentTestUtil.assertGetAllContentLibrarySucceeds, _, _, null, _), deleteSucceeds(adminRestContext, deleterRestContext, contentId, callback) { ContentTestUtil.assertDeleteContentSucceeds(deleterRestContext, contentId, callback); } @@ -155,26 +150,10 @@ describe('Invitations', () => { shareFails: DiscussionsTestUtil.assertShareDiscussionFails, setRolesSucceeds: DiscussionsTestUtil.assertUpdateDiscussionMembersSucceeds, setRolesFails: DiscussionsTestUtil.assertUpdateDiscussionMembersFails, - getMembersSucceeds: _partialWithFns( - DiscussionsTestUtil.getAllDiscussionMembers, - _, - _, - null, - _ - ), - getLibrarySucceeds: _partialWithFns( - DiscussionsTestUtil.assertGetAllDiscussionsLibrarySucceeds, - _, - _, - null, - _ - ), + getMembersSucceeds: _partialWithFns(DiscussionsTestUtil.getAllDiscussionMembers, _, _, null, _), + getLibrarySucceeds: _partialWithFns(DiscussionsTestUtil.assertGetAllDiscussionsLibrarySucceeds, _, _, null, _), deleteSucceeds(adminRestContext, deleterRestContext, discussionId, callback) { - DiscussionsTestUtil.assertDeleteDiscussionSucceeds( - deleterRestContext, - discussionId, - callback - ); + DiscussionsTestUtil.assertDeleteDiscussionSucceeds(deleterRestContext, discussionId, callback); } }, folder: { @@ -203,20 +182,8 @@ describe('Invitations', () => { shareFails: FoldersTestUtil.assertShareFolderFails, setRolesSucceeds: FoldersTestUtil.assertUpdateFolderMembersSucceeds, setRolesFails: FoldersTestUtil.assertUpdateFolderMembersFails, - getMembersSucceeds: _partialWithFns( - FoldersTestUtil.assertGetAllFolderMembersSucceeds, - _, - _, - null, - _ - ), - getLibrarySucceeds: _partialWithFns( - FoldersTestUtil.assertGetAllFoldersLibrarySucceeds, - _, - _, - null, - _ - ), + getMembersSucceeds: _partialWithFns(FoldersTestUtil.assertGetAllFolderMembersSucceeds, _, _, null, _), + getLibrarySucceeds: _partialWithFns(FoldersTestUtil.assertGetAllFoldersLibrarySucceeds, _, _, null, _), deleteSucceeds(adminRestContext, deleterRestContext, folderId, callback) { FoldersTestUtil.assertDeleteFolderSucceeds(deleterRestContext, folderId, true, callback); } @@ -247,29 +214,17 @@ describe('Invitations', () => { ), setRolesSucceeds: PrincipalsTestUtil.assertSetGroupMembersSucceeds, setRolesFails: PrincipalsTestUtil.assertSetGroupMembersFails, - getMembersSucceeds: _partialWithFns( - PrincipalsTestUtil.assertGetAllMembersLibrarySucceeds, - _, - _, - null, - _ - ), - getLibrarySucceeds: _partialWithFns( - PrincipalsTestUtil.assertGetAllMembershipsLibrarySucceeds, - _, - _, - null, - _ - ), + getMembersSucceeds: _partialWithFns(PrincipalsTestUtil.assertGetAllMembersLibrarySucceeds, _, _, null, _), + getLibrarySucceeds: _partialWithFns(PrincipalsTestUtil.assertGetAllMembershipsLibrarySucceeds, _, _, null, _), deleteSucceeds: PrincipalsTestUtil.assertDeleteGroupSucceeds, restoreSucceeds: PrincipalsTestUtil.assertRestoreGroupSucceeds } }; /*! - * Build a library of the "member" role for all known resources types. "manager" is common for - * all resources - */ + * Build a library of the "member" role for all known resources types. "manager" is common for + * all resources + */ const resourceMemberRoles = { content: 'viewer', discussion: 'member', @@ -278,9 +233,9 @@ describe('Invitations', () => { }; /*! - * Build a library of expected activity information after an invitation is accepted for each - * resource type - */ + * Build a library of expected activity information after an invitation is accepted for each + * resource type + */ const resourceAcceptActivityInfo = { content: { activityType: 'content-share', @@ -301,8 +256,8 @@ describe('Invitations', () => { }; /*! - * Define the library names for each respective resource type - */ + * Define the library names for each respective resource type + */ const resourceLibraryInfo = { content: 'content-library', discussion: 'discussion-library', @@ -714,52 +669,42 @@ describe('Invitations', () => { * Test that verifies the invitation email can be resent */ it('verify it resends an aggregated email for each resource type', callback => { - TestsUtil.generateTestUsers( - camAdminRestContext, - 2, - (err, users, creatingUser, acceptingUser) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 2, (err, users, creatingUser, acceptingUser) => { + assert.ok(!err); - // Create one of each resource type with the creating user - const email = _emailForTenant(global.oaeTests.tenants.cam); - _createOneOfEachResourceType(creatingUser, 'public', [email], [], resources => { - // Collect all the invitations, we're going to resend them instead - EmailTestUtil.collectAndFetchAllEmails(messages => { - assert.strictEqual(messages.length, 1); + // Create one of each resource type with the creating user + const email = _emailForTenant(global.oaeTests.tenants.cam); + _createOneOfEachResourceType(creatingUser, 'public', [email], [], resources => { + // Collect all the invitations, we're going to resend them instead + EmailTestUtil.collectAndFetchAllEmails(messages => { + assert.strictEqual(messages.length, 1); - // Once all invitations are resent, then accept them - const _done = _.chain(resources) - .size() - .after(() => { - const assertions = { role: 'manager', membersSize: 2, librarySize: 1 }; - _assertAcceptEmailInvitation( - creatingUser, - acceptingUser, - resources, - assertions, - () => { - return callback(); - } - ); - }) - .value(); + // Once all invitations are resent, then accept them + const _done = _.chain(resources) + .size() + .after(() => { + const assertions = { role: 'manager', membersSize: 2, librarySize: 1 }; + _assertAcceptEmailInvitation(creatingUser, acceptingUser, resources, assertions, () => { + return callback(); + }); + }) + .value(); - // Resend all invitations - _.each(resources, resource => { - AuthzTestUtil.assertResendInvitationSucceeds( - creatingUser.restContext, - resource.resourceType, - resource.id, - email, - () => { - return _done(); - } - ); - }); + // Resend all invitations + _.each(resources, resource => { + AuthzTestUtil.assertResendInvitationSucceeds( + creatingUser.restContext, + resource.resourceType, + resource.id, + email, + () => { + return _done(); + } + ); }); }); - } - ); + }); + }); }); }); @@ -789,9 +734,7 @@ describe('Invitations', () => { }); // Grab the invitation link from the messages - const cambridgeInvitationUrl = AuthzTestUtil.parseInvitationUrlFromMessage( - cambridgeMessage - ); + const cambridgeInvitationUrl = AuthzTestUtil.parseInvitationUrlFromMessage(cambridgeMessage); const guestInvitationUrl = AuthzTestUtil.parseInvitationUrlFromMessage(guestMessage); // Ensure the links are to the proper tenancy @@ -812,61 +755,53 @@ describe('Invitations', () => { const fns = resourceFns.content; const tenantAlias = TenantsTestUtil.generateTestTenantAlias(); const tenantHost = TenantsTestUtil.generateTestTenantHost(); - TestsUtil.createTenantWithAdmin( - tenantAlias, - tenantHost, - (err, tenant, tenantAdminRestContext) => { + TestsUtil.createTenantWithAdmin(tenantAlias, tenantHost, (err, tenant, tenantAdminRestContext) => { + assert.ok(!err); + TestsUtil.generateTestUsers(tenantAdminRestContext, 1, (err, users, user) => { assert.ok(!err); - TestsUtil.generateTestUsers(tenantAdminRestContext, 1, (err, users, user) => { - assert.ok(!err); - // Use an email that differs from the tenant email domain - // only by case - const matchingEmailDomain = tenantHost.toUpperCase(); - const matchingEmail = _emailForDomain(matchingEmailDomain); - // eslint-disable-next-line no-unused-vars - fns.createSucceeds(user.restContext, 'public', [matchingEmail], [], resource => { - EmailTestUtil.collectAndFetchAllEmails(messages => { - assert.strictEqual(messages.length, 1); + // Use an email that differs from the tenant email domain + // only by case + const matchingEmailDomain = tenantHost.toUpperCase(); + const matchingEmail = _emailForDomain(matchingEmailDomain); + // eslint-disable-next-line no-unused-vars + fns.createSucceeds(user.restContext, 'public', [matchingEmail], [], resource => { + EmailTestUtil.collectAndFetchAllEmails(messages => { + assert.strictEqual(messages.length, 1); - const message = _.first(messages); - const toEmail = message.headers.to; - const invitationUrl = AuthzTestUtil.parseInvitationUrlFromMessage(message); + const message = _.first(messages); + const toEmail = message.headers.to; + const invitationUrl = AuthzTestUtil.parseInvitationUrlFromMessage(message); - assert.strictEqual(toEmail, matchingEmail.toLowerCase()); - assert.strictEqual(invitationUrl.host, tenant.host); + assert.strictEqual(toEmail, matchingEmail.toLowerCase()); + assert.strictEqual(invitationUrl.host, tenant.host); - return callback(); - }); + return callback(); }); }); - } - ); + }); + }); }); /** * Test that verifies it sends an aggregated email for all resource types */ it('verify it sends an aggregated email for each resource type', callback => { - TestsUtil.generateTestUsers( - camAdminRestContext, - 2, - (err, users, creatingUser, acceptingUser) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 2, (err, users, creatingUser, acceptingUser) => { + assert.ok(!err); - // Generate the email to invite and ensure we start with an empty email queue - const email = _emailForTenant(global.oaeTests.tenants.cam); - // Create one of each resource type with the creating user - _createOneOfEachResourceType(creatingUser, 'public', [email], [], resources => { - // Ensure when the invitation is accepted from the email, all resources are - // added to the user's respective libraries - const assertions = { role: 'manager', membersSize: 2, librarySize: 1 }; - _assertAcceptEmailInvitation(creatingUser, acceptingUser, resources, assertions, () => { - return callback(); - }); + // Generate the email to invite and ensure we start with an empty email queue + const email = _emailForTenant(global.oaeTests.tenants.cam); + // Create one of each resource type with the creating user + _createOneOfEachResourceType(creatingUser, 'public', [email], [], resources => { + // Ensure when the invitation is accepted from the email, all resources are + // added to the user's respective libraries + const assertions = { role: 'manager', membersSize: 2, librarySize: 1 }; + _assertAcceptEmailInvitation(creatingUser, acceptingUser, resources, assertions, () => { + return callback(); }); - } - ); + }); + }); }); describe('Content', () => { @@ -1177,120 +1112,80 @@ describe('Invitations', () => { resources.push(resource1); let emailAssertions = { role: 'manager', membersSize: 2, librarySize: 1 }; - _assertAcceptEmailInvitation( - inviterUserInfo, - invitedUserInfo, - [resource1], - emailAssertions, - () => { - _assertAdaptedActivities( - inviterUserInfo, - invitedUserInfo, - otherUserInfo, - resources, - assertions, - () => { - // Share a 2nd of this resource and accept the invitation, while asserting the "accept invitation" for the 2 items in the aggregated feed - fns.createSucceeds( - inviterUserInfo.restContext, - 'private', - [email], - [], - resource2 => { - resources.push(resource2); - - emailAssertions = { role: 'manager', membersSize: 2, librarySize: 2 }; - _assertAcceptEmailInvitation( - inviterUserInfo, - invitedUserInfo, - [resource2], - emailAssertions, - () => { - _assertAdaptedActivities( - inviterUserInfo, - invitedUserInfo, - otherUserInfo, - resources, - assertions, - () => { - // Share a 3rd of this resource and accept the invitation, while asserting the "accept invitation" for the 3 items in the aggregated feed - fns.createSucceeds( - inviterUserInfo.restContext, - 'private', - [email], - [], - resource3 => { - resources.push(resource3); - - emailAssertions = { - role: 'manager', - membersSize: 2, - librarySize: 3 - }; - _assertAcceptEmailInvitation( - inviterUserInfo, - invitedUserInfo, - [resource3], - emailAssertions, - () => { - _assertAdaptedActivities( - inviterUserInfo, - invitedUserInfo, - otherUserInfo, - resources, - assertions, - () => { - // If we don't want to check that the resource received activities, then we're done - if (!assertions.resourceActivity) { - return callback(); - } - - // Ensure the resource received an activity as well - ActivityTestUtil.collectAndGetActivityStream( - inviterUserInfo.restContext, - resource1.id, - null, - (err, result) => { - assert.ok(!err); - assert.ok(result); - assert.ok(_.isArray(result.items)); - - // Has a create activity and an invitation accept activity - assert.strictEqual(result.items.length, 2); - - const activity = result.items[0]; - assert.strictEqual( - activity.actor['oae:id'], - invitedUserInfo.user.id - ); - assert.strictEqual( - activity.object['oae:id'], - inviterUserInfo.user.id - ); - assert.strictEqual( - activity.target['oae:id'], - resource1.id - ); - - return callback(); - } - ); - } - ); - } - ); + _assertAcceptEmailInvitation(inviterUserInfo, invitedUserInfo, [resource1], emailAssertions, () => { + _assertAdaptedActivities(inviterUserInfo, invitedUserInfo, otherUserInfo, resources, assertions, () => { + // Share a 2nd of this resource and accept the invitation, while asserting the "accept invitation" for the 2 items in the aggregated feed + fns.createSucceeds(inviterUserInfo.restContext, 'private', [email], [], resource2 => { + resources.push(resource2); + + emailAssertions = { role: 'manager', membersSize: 2, librarySize: 2 }; + _assertAcceptEmailInvitation(inviterUserInfo, invitedUserInfo, [resource2], emailAssertions, () => { + _assertAdaptedActivities( + inviterUserInfo, + invitedUserInfo, + otherUserInfo, + resources, + assertions, + () => { + // Share a 3rd of this resource and accept the invitation, while asserting the "accept invitation" for the 3 items in the aggregated feed + fns.createSucceeds(inviterUserInfo.restContext, 'private', [email], [], resource3 => { + resources.push(resource3); + + emailAssertions = { + role: 'manager', + membersSize: 2, + librarySize: 3 + }; + _assertAcceptEmailInvitation( + inviterUserInfo, + invitedUserInfo, + [resource3], + emailAssertions, + () => { + _assertAdaptedActivities( + inviterUserInfo, + invitedUserInfo, + otherUserInfo, + resources, + assertions, + () => { + // If we don't want to check that the resource received activities, then we're done + if (!assertions.resourceActivity) { + return callback(); } - ); - } - ); - } - ); + + // Ensure the resource received an activity as well + ActivityTestUtil.collectAndGetActivityStream( + inviterUserInfo.restContext, + resource1.id, + null, + (err, result) => { + assert.ok(!err); + assert.ok(result); + assert.ok(_.isArray(result.items)); + + // Has a create activity and an invitation accept activity + assert.strictEqual(result.items.length, 2); + + const activity = result.items[0]; + assert.strictEqual(activity.actor['oae:id'], invitedUserInfo.user.id); + assert.strictEqual(activity.object['oae:id'], inviterUserInfo.user.id); + assert.strictEqual(activity.target['oae:id'], resource1.id); + + return callback(); + } + ); + } + ); + } + ); + }); } ); - } - ); - } - ); + }); + }); + }); + }); }); } ); @@ -1298,163 +1193,100 @@ describe('Invitations', () => { const _testInvitationAcceptEmailSummary = function(resourceType, assertions, callback) { const fns = resourceFns[resourceType]; - TestsUtil.generateTestUsers( - camAdminRestContext, - 2, - (err, users, sharingUserInfo, acceptingUserInfo) => { - assert.ok(!err); - const email = _emailForTenant(global.oaeTests.tenants.cam); + TestsUtil.generateTestUsers(camAdminRestContext, 2, (err, users, sharingUserInfo, acceptingUserInfo) => { + assert.ok(!err); + const email = _emailForTenant(global.oaeTests.tenants.cam); - // Share 1 of this resource and accept the invitation, while getting the - // "accept invitation" activity email sent to the sharer - fns.createSucceeds(sharingUserInfo.restContext, 'private', [email], [], resourceA => { - let emailAssertions = { role: 'manager', membersSize: 2, librarySize: 1 }; - _assertAcceptEmailInvitation( - sharingUserInfo, - acceptingUserInfo, - [resourceA], - emailAssertions, - message => { - const activities = ActivityTestUtil.parseActivityHtml(message.html); - assert.strictEqual(activities.length, 1); - - _assertAcceptInvitationContainsResourceHtml(activities[0].summary.html, resourceA); - _.each(assertions.oneResourceSummaryMatch, oneResourceSummaryMatch => { - _assertContains(activities[0].summary.html, oneResourceSummaryMatch); - }); + // Share 1 of this resource and accept the invitation, while getting the + // "accept invitation" activity email sent to the sharer + fns.createSucceeds(sharingUserInfo.restContext, 'private', [email], [], resourceA => { + let emailAssertions = { role: 'manager', membersSize: 2, librarySize: 1 }; + _assertAcceptEmailInvitation(sharingUserInfo, acceptingUserInfo, [resourceA], emailAssertions, message => { + const activities = ActivityTestUtil.parseActivityHtml(message.html); + assert.strictEqual(activities.length, 1); - // Share 2 of this resource and accept the invitation, while getting the - // "accept invitation" activity email sent to the sharer - fns.createSucceeds( - sharingUserInfo.restContext, - 'private', - [email], - [], - resourceB1 => { - fns.createSucceeds( - sharingUserInfo.restContext, - 'private', - [email], - [], - resourceB2 => { - emailAssertions = { role: 'manager', membersSize: 2, librarySize: 3 }; - _assertAcceptEmailInvitation( - sharingUserInfo, - acceptingUserInfo, - [resourceB1, resourceB2], - emailAssertions, - message => { - const activities = ActivityTestUtil.parseActivityHtml(message.html); - assert.strictEqual(activities.length, 1); - - _assertAcceptInvitationContainsResourceHtml( - activities[0].summary.html, - resourceB1 - ); - _assertAcceptInvitationContainsResourceHtml( - activities[0].summary.html, - resourceB2 - ); - _.each(assertions.twoResourceSummaryMatch, twoResourceSummaryMatch => { - _assertContains(activities[0].summary.html, twoResourceSummaryMatch); - }); + _assertAcceptInvitationContainsResourceHtml(activities[0].summary.html, resourceA); + _.each(assertions.oneResourceSummaryMatch, oneResourceSummaryMatch => { + _assertContains(activities[0].summary.html, oneResourceSummaryMatch); + }); - // Share 3 of this resource and accept the invitation, while getting the - // "accept invitation" activity email sent to the sharer - fns.createSucceeds( - sharingUserInfo.restContext, - 'private', - [email], - [], - resourceC1 => { - fns.createSucceeds( - sharingUserInfo.restContext, - 'private', - [email], - [], - resourceC2 => { - fns.createSucceeds( - sharingUserInfo.restContext, - 'private', - [email], - [], - resourceC3 => { - const resourceCs = [resourceC1, resourceC2, resourceC3]; - emailAssertions = { - role: 'manager', - membersSize: 2, - librarySize: 6 - }; - _assertAcceptEmailInvitation( - sharingUserInfo, - acceptingUserInfo, - resourceCs, - emailAssertions, - message => { - const activities = ActivityTestUtil.parseActivityHtml( - message.html - ); - assert.strictEqual(activities.length, 1); - - // Ensure the summary has at exactly one of the resources - const numMatchesDisplayName = _.chain(resourceCs) - .pluck('displayName') - .filter(displayName => { - return ( - activities[0].summary.html.indexOf(displayName) !== - -1 - ); - }) - .size() - .value(); - const numMatchesProfilePath = _.chain(resourceCs) - .pluck('profilePath') - .filter(profilePath => { - return ( - activities[0].summary.html.indexOf(profilePath) !== - -1 - ); - }) - .size() - .value(); - - assert.strictEqual(numMatchesDisplayName, 1); - assert.strictEqual(numMatchesProfilePath, 1); - - _.each( - assertions.threeResourceSummaryMatch, - threeResourceSummaryMatch => { - _assertContains( - activities[0].summary.html, - threeResourceSummaryMatch - ); - } - ); - _assertContains( - activities[0].summary.html, - 'and 2 others' - ); - - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + // Share 2 of this resource and accept the invitation, while getting the + // "accept invitation" activity email sent to the sharer + fns.createSucceeds(sharingUserInfo.restContext, 'private', [email], [], resourceB1 => { + fns.createSucceeds(sharingUserInfo.restContext, 'private', [email], [], resourceB2 => { + emailAssertions = { role: 'manager', membersSize: 2, librarySize: 3 }; + _assertAcceptEmailInvitation( + sharingUserInfo, + acceptingUserInfo, + [resourceB1, resourceB2], + emailAssertions, + message => { + const activities = ActivityTestUtil.parseActivityHtml(message.html); + assert.strictEqual(activities.length, 1); + + _assertAcceptInvitationContainsResourceHtml(activities[0].summary.html, resourceB1); + _assertAcceptInvitationContainsResourceHtml(activities[0].summary.html, resourceB2); + _.each(assertions.twoResourceSummaryMatch, twoResourceSummaryMatch => { + _assertContains(activities[0].summary.html, twoResourceSummaryMatch); + }); + + // Share 3 of this resource and accept the invitation, while getting the + // "accept invitation" activity email sent to the sharer + fns.createSucceeds(sharingUserInfo.restContext, 'private', [email], [], resourceC1 => { + fns.createSucceeds(sharingUserInfo.restContext, 'private', [email], [], resourceC2 => { + fns.createSucceeds(sharingUserInfo.restContext, 'private', [email], [], resourceC3 => { + const resourceCs = [resourceC1, resourceC2, resourceC3]; + emailAssertions = { + role: 'manager', + membersSize: 2, + librarySize: 6 + }; + _assertAcceptEmailInvitation( + sharingUserInfo, + acceptingUserInfo, + resourceCs, + emailAssertions, + message => { + const activities = ActivityTestUtil.parseActivityHtml(message.html); + assert.strictEqual(activities.length, 1); + + // Ensure the summary has at exactly one of the resources + const numMatchesDisplayName = _.chain(resourceCs) + .pluck('displayName') + .filter(displayName => { + return activities[0].summary.html.indexOf(displayName) !== -1; + }) + .size() + .value(); + const numMatchesProfilePath = _.chain(resourceCs) + .pluck('profilePath') + .filter(profilePath => { + return activities[0].summary.html.indexOf(profilePath) !== -1; + }) + .size() + .value(); + + assert.strictEqual(numMatchesDisplayName, 1); + assert.strictEqual(numMatchesProfilePath, 1); + + _.each(assertions.threeResourceSummaryMatch, threeResourceSummaryMatch => { + _assertContains(activities[0].summary.html, threeResourceSummaryMatch); + }); + _assertContains(activities[0].summary.html, 'and 2 others'); + + return callback(); + } + ); + }); + }); + }); } ); - } - ); + }); + }); }); - } - ); + }); + }); }; const _testInvitationEmailHtmlTargets = function(resourceType, assertions, callback) { @@ -1491,54 +1323,37 @@ describe('Invitations', () => { // 3 content items summary fns.createSucceeds(sharingUser.restContext, 'public', [email], [], resourceC1 => { fns.createSucceeds(sharingUser.restContext, 'public', [email], [], resourceC2 => { - fns.createSucceeds( - sharingUser.restContext, - 'public', - [email], - [], - resourceC3 => { - EmailTestUtil.collectAndFetchAllEmails(messages => { - assert.strictEqual(messages.length, 1); - - const activities = ActivityTestUtil.parseActivityHtml(messages[0].html); - assert.strictEqual(activities.length, 1); - - // Ensure the summary has at exactly one of the resources - const numMatchesDisplayName = _.chain([ - resourceC1, - resourceC2, - resourceC3 - ]) - .pluck('displayName') - .filter(displayName => { - return activities[0].summary.html.indexOf(displayName) !== -1; - }) - .size() - .value(); - const numMatchesProfilePath = _.chain([ - resourceC1, - resourceC2, - resourceC3 - ]) - .pluck('profilePath') - .filter(profilePath => { - return activities[0].summary.html.indexOf(profilePath) !== -1; - }) - .size() - .value(); - - assert.strictEqual(numMatchesDisplayName, 1); - assert.strictEqual(numMatchesProfilePath, 0); - _assertContains( - activities[0].summary.html, - assertions.threeResourceSummaryMatch - ); - _assertContains(activities[0].summary.html, 'and 2 others'); - - return callback(); - }); - } - ); + fns.createSucceeds(sharingUser.restContext, 'public', [email], [], resourceC3 => { + EmailTestUtil.collectAndFetchAllEmails(messages => { + assert.strictEqual(messages.length, 1); + + const activities = ActivityTestUtil.parseActivityHtml(messages[0].html); + assert.strictEqual(activities.length, 1); + + // Ensure the summary has at exactly one of the resources + const numMatchesDisplayName = _.chain([resourceC1, resourceC2, resourceC3]) + .pluck('displayName') + .filter(displayName => { + return activities[0].summary.html.indexOf(displayName) !== -1; + }) + .size() + .value(); + const numMatchesProfilePath = _.chain([resourceC1, resourceC2, resourceC3]) + .pluck('profilePath') + .filter(profilePath => { + return activities[0].summary.html.indexOf(profilePath) !== -1; + }) + .size() + .value(); + + assert.strictEqual(numMatchesDisplayName, 1); + assert.strictEqual(numMatchesProfilePath, 0); + _assertContains(activities[0].summary.html, assertions.threeResourceSummaryMatch); + _assertContains(activities[0].summary.html, 'and 2 others'); + + return callback(); + }); + }); }); }); }); @@ -1586,10 +1401,7 @@ describe('Invitations', () => { const { subject } = message.headers; assert.strictEqual( subject, - util.format( - '%s has invited you to collaborate', - sharingUser1.user.displayName - ) + util.format('%s has invited you to collaborate', sharingUser1.user.displayName) ); // Verify that with 2 activities and 2 actors, the @@ -1613,14 +1425,8 @@ describe('Invitations', () => { const message = messages[0]; const { subject } = message.headers; - assert.notStrictEqual( - subject.indexOf(sharingUser1.user.displayName), - -1 - ); - assert.notStrictEqual( - subject.indexOf(sharingUser2.user.displayName), - -1 - ); + assert.notStrictEqual(subject.indexOf(sharingUser1.user.displayName), -1); + assert.notStrictEqual(subject.indexOf(sharingUser2.user.displayName), -1); // Verify that with 3 activities and 3 actors, the // subject shows 1 of the actors and indicates there @@ -1652,11 +1458,7 @@ describe('Invitations', () => { const message = messages[0]; const { subject } = message.headers; - const numMatches = _.chain([ - sharingUser1, - sharingUser2, - sharingUser3 - ]) + const numMatches = _.chain([sharingUser1, sharingUser2, sharingUser3]) .pluck('user') .pluck('displayName') .filter(displayName => { @@ -1665,10 +1467,7 @@ describe('Invitations', () => { .size() .value(); assert.strictEqual(numMatches, 1); - assert.notStrictEqual( - subject.indexOf('and 2 others'), - -1 - ); + assert.notStrictEqual(subject.indexOf('and 2 others'), -1); return callback(); }); } @@ -1693,17 +1492,17 @@ describe('Invitations', () => { }; /*! - * Ensure that when a resource is soft deleted (i.e., it's delete functionality is that of a - * "mark" as deleted which can be restored), an invitation that is accepted for it doesn't - * result in the resource being added to the user's library. - * - * Furthermore, ensure that when the resource is restored, the user who accepted the resource - * then gets it in their resource library. - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure that when a resource is soft deleted (i.e., it's delete functionality is that of a + * "mark" as deleted which can be restored), an invitation that is accepted for it doesn't + * result in the resource being added to the user's library. + * + * Furthermore, ensure that when the resource is restored, the user who accepted the resource + * then gets it in their resource library. + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testSoftDeleteForAccept = function(resourceType, callback) { const fns = resourceFns[resourceType]; _testHardDeleteForAccept(resourceType, (creatingUserInfo, acceptingUserInfo, resource) => { @@ -1718,626 +1517,474 @@ describe('Invitations', () => { }; /*! - * Ensure that when a resource is hard deleted (i.e., it is deleted from the database and - * cannot be restored), an invitation that is accepted for it doesn't result in the resource - * being added to the user's library - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure that when a resource is hard deleted (i.e., it is deleted from the database and + * cannot be restored), an invitation that is accepted for it doesn't result in the resource + * being added to the user's library + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testHardDeleteForAccept = function(resourceType, callback) { const fns = resourceFns[resourceType]; // Create a resource with an email invited into it let email = _emailForTenant(global.oaeTests.tenants.cam); - TestsUtil.generateTestUsers( - camAdminRestContext, - 2, - (err, users, creatingUserInfo, acceptingUserInfo) => { - assert.ok(!err); - - // Create the resource, sending an invite to the target user - fns.createSucceeds(creatingUserInfo.restContext, 'public', [email], [], resource => { - email = email.toLowerCase(); + TestsUtil.generateTestUsers(camAdminRestContext, 2, (err, users, creatingUserInfo, acceptingUserInfo) => { + assert.ok(!err); - EmailTestUtil.collectAndFetchAllEmails(messages => { - assert.strictEqual(messages.length, 1); + // Create the resource, sending an invite to the target user + fns.createSucceeds(creatingUserInfo.restContext, 'public', [email], [], resource => { + email = email.toLowerCase(); - const message = _.first(messages); - const token = url.parse( - AuthzTestUtil.parseInvitationUrlFromMessage(message).query.url, - true - ).query.invitationToken; + EmailTestUtil.collectAndFetchAllEmails(messages => { + assert.strictEqual(messages.length, 1); - // Delete the resource before we have a chance to accept - fns.deleteSucceeds( - camAdminRestContext, - creatingUserInfo.restContext, - resource.id, - () => { - // Now accept the invitation, ensuring no resources are reported as being - // added - AuthzTestUtil.assertAcceptInvitationSucceeds( - acceptingUserInfo.restContext, - token, - result => { - assert.strictEqual(result.email, email); - assert.strictEqual(result.resources.length, 0); - - // Ensure nothing went into the user's library - fns.getLibrarySucceeds( - acceptingUserInfo.restContext, - acceptingUserInfo.user.id, - libraryItems => { - assert.strictEqual(libraryItems.length, 0); - return callback(creatingUserInfo, acceptingUserInfo, resource); - } - ); - } - ); - } - ); + const message = _.first(messages); + const token = url.parse(AuthzTestUtil.parseInvitationUrlFromMessage(message).query.url, true).query + .invitationToken; + + // Delete the resource before we have a chance to accept + fns.deleteSucceeds(camAdminRestContext, creatingUserInfo.restContext, resource.id, () => { + // Now accept the invitation, ensuring no resources are reported as being + // added + AuthzTestUtil.assertAcceptInvitationSucceeds(acceptingUserInfo.restContext, token, result => { + assert.strictEqual(result.email, email); + assert.strictEqual(result.resources.length, 0); + + // Ensure nothing went into the user's library + fns.getLibrarySucceeds(acceptingUserInfo.restContext, acceptingUserInfo.user.id, libraryItems => { + assert.strictEqual(libraryItems.length, 0); + return callback(creatingUserInfo, acceptingUserInfo, resource); + }); + }); }); }); - } - ); + }); + }); }; /*! - * Ensure that the appropriate activity is sent to a user when they accept an invitation for - * any resource type. The resources that are created will be of the specified visibility, to - * make it simple to create multiple variations of this test for different resource - * visibilities - * - * @param {String} visibility The visibility to use on the created resources - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure that the appropriate activity is sent to a user when they accept an invitation for + * any resource type. The resources that are created will be of the specified visibility, to + * make it simple to create multiple variations of this test for different resource + * visibilities + * + * @param {String} visibility The visibility to use on the created resources + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testActivityVisibilityForAccept = function(visibility, callback) { - TestsUtil.generateTestUsers( - camAdminRestContext, - 2, - (err, users, creatingUserInfo, acceptingUserInfo) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 2, (err, users, creatingUserInfo, acceptingUserInfo) => { + assert.ok(!err); - const email = _emailForTenant(global.oaeTests.tenants.cam); - _createOneOfEachResourceType(creatingUserInfo, visibility, [email], [], resources => { - const assertions = { role: 'manager', membersSize: 2, librarySize: 1 }; - _assertAcceptEmailInvitation( - creatingUserInfo, - acceptingUserInfo, - resources, - assertions, - () => { - ActivityTestUtil.collectAndGetActivityStream( - acceptingUserInfo.restContext, - acceptingUserInfo.user.id, - null, - (err, result) => { - assert.ok(!err); - - const activities = result.items; - - // Ensure we have one accept activity for each resource - assert.strictEqual(activities.length, _.size(resourceAcceptActivityInfo)); - _.each(resources, resource => { - // eslint-disable-next-line no-unused-expressions - resourceAcceptActivityInfo[resource.resourceType]; - const matchingActivities = _.filter(activities, activity => { - return ( - activity['oae:activityType'] === 'invitation-accept' && - activity.verb === 'accept' && - activity.actor['oae:id'] === acceptingUserInfo.user.id && - activity.object['oae:id'] === creatingUserInfo.user.id && - activity.target['oae:id'] === resource.id - ); - }); + const email = _emailForTenant(global.oaeTests.tenants.cam); + _createOneOfEachResourceType(creatingUserInfo, visibility, [email], [], resources => { + const assertions = { role: 'manager', membersSize: 2, librarySize: 1 }; + _assertAcceptEmailInvitation(creatingUserInfo, acceptingUserInfo, resources, assertions, () => { + ActivityTestUtil.collectAndGetActivityStream( + acceptingUserInfo.restContext, + acceptingUserInfo.user.id, + null, + (err, result) => { + assert.ok(!err); + + const activities = result.items; + + // Ensure we have one accept activity for each resource + assert.strictEqual(activities.length, _.size(resourceAcceptActivityInfo)); + _.each(resources, resource => { + // eslint-disable-next-line no-unused-expressions + resourceAcceptActivityInfo[resource.resourceType]; + const matchingActivities = _.filter(activities, activity => { + return ( + activity['oae:activityType'] === 'invitation-accept' && + activity.verb === 'accept' && + activity.actor['oae:id'] === acceptingUserInfo.user.id && + activity.object['oae:id'] === creatingUserInfo.user.id && + activity.target['oae:id'] === resource.id + ); + }); - assert.ok(matchingActivities); - assert.strictEqual(matchingActivities.length, 1); - }); + assert.ok(matchingActivities); + assert.strictEqual(matchingActivities.length, 1); + }); - return callback(); - } - ); + return callback(); } ); }); - } - ); + }); + }); }; /*! - * Ensure that an email is sent for the specified resource type regardless of its visibility - * when invited VIA a "create" action. This test will ensure that resources of the specified - * type will send an invitation email to the email that was invited, and that the information in - * the "accept" link can be used to accept the invitation and gain access to the resources - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure that an email is sent for the specified resource type regardless of its visibility + * when invited VIA a "create" action. This test will ensure that resources of the specified + * type will send an invitation email to the email that was invited, and that the information in + * the "accept" link can be used to accept the invitation and gain access to the resources + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationEmailVisibilityForCreate = function(resourceType, callback) { const fns = resourceFns[resourceType]; - TestsUtil.generateTestUsers( - camAdminRestContext, - 2, - (err, users, creatingUser, acceptingUser) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 2, (err, users, creatingUser, acceptingUser) => { + assert.ok(!err); - const email = _emailForTenant(global.oaeTests.tenants.cam); - fns.createSucceeds(creatingUser.restContext, 'public', [email], [], resource1 => { - fns.createSucceeds(creatingUser.restContext, 'loggedin', [email], [], resource2 => { - fns.createSucceeds(creatingUser.restContext, 'private', [email], [], resource3 => { - const assertions = { role: 'manager', membersSize: 2, librarySize: 3 }; - _assertAcceptEmailInvitation( - creatingUser, - acceptingUser, - [resource1, resource2, resource3], - assertions, - () => { - return callback(); - } - ); - }); + const email = _emailForTenant(global.oaeTests.tenants.cam); + fns.createSucceeds(creatingUser.restContext, 'public', [email], [], resource1 => { + fns.createSucceeds(creatingUser.restContext, 'loggedin', [email], [], resource2 => { + fns.createSucceeds(creatingUser.restContext, 'private', [email], [], resource3 => { + const assertions = { role: 'manager', membersSize: 2, librarySize: 3 }; + _assertAcceptEmailInvitation( + creatingUser, + acceptingUser, + [resource1, resource2, resource3], + assertions, + () => { + return callback(); + } + ); }); }); - } - ); + }); + }); }; /*! - * Ensure that an email is sent for the specified resource type regardless of its visibility - * when invited VIA a "set roles" action. This test will ensure that resources of the specified - * type will send an invitation email to the email that was invited, and that the information in - * the "accept" link can be used to accept the invitation and gain access to the resources - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure that an email is sent for the specified resource type regardless of its visibility + * when invited VIA a "set roles" action. This test will ensure that resources of the specified + * type will send an invitation email to the email that was invited, and that the information in + * the "accept" link can be used to accept the invitation and gain access to the resources + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationEmailVisibilityForSetRoles = function(resourceType, callback) { const fns = resourceFns[resourceType]; - TestsUtil.generateTestUsers( - camAdminRestContext, - 2, - (err, users, creatingUser, acceptingUser) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 2, (err, users, creatingUser, acceptingUser) => { + assert.ok(!err); - const email = _emailForTenant(global.oaeTests.tenants.cam); + const email = _emailForTenant(global.oaeTests.tenants.cam); - // Create a resource of each visibility - fns.createSucceeds(creatingUser.restContext, 'public', [], [], resource1 => { - fns.createSucceeds(creatingUser.restContext, 'loggedin', [], [], resource2 => { - fns.createSucceeds(creatingUser.restContext, 'private', [], [], resource3 => { - const roleChange = _.object([[email, 'manager']]); + // Create a resource of each visibility + fns.createSucceeds(creatingUser.restContext, 'public', [], [], resource1 => { + fns.createSucceeds(creatingUser.restContext, 'loggedin', [], [], resource2 => { + fns.createSucceeds(creatingUser.restContext, 'private', [], [], resource3 => { + const roleChange = _.object([[email, 'manager']]); - // Set the accepting user as a manager on all 3 using set roles - fns.setRolesSucceeds( - creatingUser.restContext, - creatingUser.restContext, - resource1.id, - roleChange, - () => { - fns.setRolesSucceeds( - creatingUser.restContext, - creatingUser.restContext, - resource2.id, - roleChange, - () => { - fns.setRolesSucceeds( - creatingUser.restContext, - creatingUser.restContext, - resource3.id, - roleChange, - () => { - // Ensure the user can accept the email invitation - const assertions = { role: 'manager', membersSize: 2, librarySize: 3 }; - _assertAcceptEmailInvitation( - creatingUser, - acceptingUser, - [resource1, resource2, resource3], - assertions, - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); + // Set the accepting user as a manager on all 3 using set roles + fns.setRolesSucceeds(creatingUser.restContext, creatingUser.restContext, resource1.id, roleChange, () => { + fns.setRolesSucceeds(creatingUser.restContext, creatingUser.restContext, resource2.id, roleChange, () => { + fns.setRolesSucceeds( + creatingUser.restContext, + creatingUser.restContext, + resource3.id, + roleChange, + () => { + // Ensure the user can accept the email invitation + const assertions = { role: 'manager', membersSize: 2, librarySize: 3 }; + _assertAcceptEmailInvitation( + creatingUser, + acceptingUser, + [resource1, resource2, resource3], + assertions, + () => { + return callback(); + } + ); + } + ); + }); }); }); }); - } - ); + }); + }); }; /*! - * Ensure that an email is sent for the specified resource type regardless of its visibility - * when invited VIA a "share" action. This test will ensure that resources of the specified - * type will send an invitation email to the email that was invited, and that the information in - * the "accept" link can be used to accept the invitation and gain access to the resources - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure that an email is sent for the specified resource type regardless of its visibility + * when invited VIA a "share" action. This test will ensure that resources of the specified + * type will send an invitation email to the email that was invited, and that the information in + * the "accept" link can be used to accept the invitation and gain access to the resources + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationEmailVisibilityForShare = function(resourceType, callback) { const fns = resourceFns[resourceType]; const memberRole = resourceMemberRoles[resourceType]; - TestsUtil.generateTestUsers( - camAdminRestContext, - 2, - (err, users, creatingUser, acceptingUser) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 2, (err, users, creatingUser, acceptingUser) => { + assert.ok(!err); - const email = _emailForTenant(global.oaeTests.tenants.cam); + const email = _emailForTenant(global.oaeTests.tenants.cam); - // Create a resource of each visibility - fns.createSucceeds(creatingUser.restContext, 'public', [], [], resource1 => { - fns.createSucceeds(creatingUser.restContext, 'loggedin', [], [], resource2 => { - fns.createSucceeds(creatingUser.restContext, 'private', [], [], resource3 => { - // Share with each resource - fns.shareSucceeds( - creatingUser.restContext, - creatingUser.restContext, - resource1.id, - [email], - () => { - fns.shareSucceeds( - creatingUser.restContext, - creatingUser.restContext, - resource2.id, - [email], + // Create a resource of each visibility + fns.createSucceeds(creatingUser.restContext, 'public', [], [], resource1 => { + fns.createSucceeds(creatingUser.restContext, 'loggedin', [], [], resource2 => { + fns.createSucceeds(creatingUser.restContext, 'private', [], [], resource3 => { + // Share with each resource + fns.shareSucceeds(creatingUser.restContext, creatingUser.restContext, resource1.id, [email], () => { + fns.shareSucceeds(creatingUser.restContext, creatingUser.restContext, resource2.id, [email], () => { + fns.shareSucceeds(creatingUser.restContext, creatingUser.restContext, resource3.id, [email], () => { + // Ensure the user can accept the email invitation + const assertions = { role: memberRole, membersSize: 2, librarySize: 3 }; + _assertAcceptEmailInvitation( + creatingUser, + acceptingUser, + [resource1, resource2, resource3], + assertions, () => { - fns.shareSucceeds( - creatingUser.restContext, - creatingUser.restContext, - resource3.id, - [email], - () => { - // Ensure the user can accept the email invitation - const assertions = { role: memberRole, membersSize: 2, librarySize: 3 }; - _assertAcceptEmailInvitation( - creatingUser, - acceptingUser, - [resource1, resource2, resource3], - assertions, - () => { - return callback(); - } - ); - } - ); + return callback(); } ); - } - ); + }); + }); }); }); }); - } - ); + }); + }); }; /*! - * Ensure that when an invitation is accepted, the response and side-effects (i.e., gaining - * access to the invited resources) work as expected - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure that when an invitation is accepted, the response and side-effects (i.e., gaining + * access to the invited resources) work as expected + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationAccept = function(resourceType, callback) { const fns = resourceFns[resourceType]; const memberRole = resourceMemberRoles[resourceType]; - TestsUtil.generateTestUsers( - camAdminRestContext, - 3, - (err, users, user0, userManager, userViewer) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 3, (err, users, user0, userManager, userViewer) => { + assert.ok(!err); - const managerEmail = _emailForTenant(global.oaeTests.tenants.cam); - const viewerEmail = _emailForTenant(global.oaeTests.tenants.cam); - - // Create a resource. 2 separate invitations will go out - fns.createSucceeds(user0.restContext, 'public', [managerEmail], [viewerEmail], resource => { - const resourceAuthzId = AuthzUtil.getAuthzId(resource); - - // Accept the manager invitation and ensure they show up in the members - AuthzTestUtil.assertAcceptInvitationForEmailSucceeds( - userManager.restContext, - managerEmail, - (result, invitations) => { - assert.strictEqual(invitations.length, 1); - - const invitation = _.first(invitations); - assert.strictEqual(invitation.resourceId, resourceAuthzId); - assert.strictEqual(invitation.email, managerEmail.toLowerCase()); - assert.strictEqual(invitation.inviterUserId, user0.user.id); - assert.strictEqual(invitation.role, 'manager'); - - let assertions = { role: 'manager', membersSize: 2, librarySize: 1 }; - _assertRole(user0, userManager, resource, assertions, () => { - // Accept the viewer invitation and ensure they show up in the members - AuthzTestUtil.assertAcceptInvitationForEmailSucceeds( - userViewer.restContext, - viewerEmail, - (result, invitations) => { - assert.strictEqual(invitations.length, 1); - - const invitation = _.first(invitations); - assert.strictEqual(invitation.resourceId, resourceAuthzId); - assert.strictEqual(invitation.email, viewerEmail.toLowerCase()); - assert.strictEqual(invitation.inviterUserId, user0.user.id); - assert.strictEqual(invitation.role, memberRole); - - assertions = { role: memberRole, membersSize: 3, librarySize: 1 }; - _assertRole(user0, userViewer, resource, assertions, () => { - return callback(); - }); - } - ); - }); - } - ); - }); - } - ); + const managerEmail = _emailForTenant(global.oaeTests.tenants.cam); + const viewerEmail = _emailForTenant(global.oaeTests.tenants.cam); + + // Create a resource. 2 separate invitations will go out + fns.createSucceeds(user0.restContext, 'public', [managerEmail], [viewerEmail], resource => { + const resourceAuthzId = AuthzUtil.getAuthzId(resource); + + // Accept the manager invitation and ensure they show up in the members + AuthzTestUtil.assertAcceptInvitationForEmailSucceeds( + userManager.restContext, + managerEmail, + (result, invitations) => { + assert.strictEqual(invitations.length, 1); + + const invitation = _.first(invitations); + assert.strictEqual(invitation.resourceId, resourceAuthzId); + assert.strictEqual(invitation.email, managerEmail.toLowerCase()); + assert.strictEqual(invitation.inviterUserId, user0.user.id); + assert.strictEqual(invitation.role, 'manager'); + + let assertions = { role: 'manager', membersSize: 2, librarySize: 1 }; + _assertRole(user0, userManager, resource, assertions, () => { + // Accept the viewer invitation and ensure they show up in the members + AuthzTestUtil.assertAcceptInvitationForEmailSucceeds( + userViewer.restContext, + viewerEmail, + (result, invitations) => { + assert.strictEqual(invitations.length, 1); + + const invitation = _.first(invitations); + assert.strictEqual(invitation.resourceId, resourceAuthzId); + assert.strictEqual(invitation.email, viewerEmail.toLowerCase()); + assert.strictEqual(invitation.inviterUserId, user0.user.id); + assert.strictEqual(invitation.role, memberRole); + + assertions = { role: memberRole, membersSize: 3, librarySize: 1 }; + _assertRole(user0, userViewer, resource, assertions, () => { + return callback(); + }); + } + ); + }); + } + ); + }); + }); }; /*! - * Ensure authorization of accepting an invitation that contains a particular resource type. - * - * This also includes authorization revolving around the changed state of the inviting user. For - * example, if the user who invited another has since been deleted, or is demoted/removed from - * the resource, the invitation should still be successful. - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure authorization of accepting an invitation that contains a particular resource type. + * + * This also includes authorization revolving around the changed state of the inviting user. For + * example, if the user who invited another has since been deleted, or is demoted/removed from + * the resource, the invitation should still be successful. + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationAcceptAuthorization = function(resourceType, callback) { const fns = resourceFns[resourceType]; const memberRole = resourceMemberRoles[resourceType]; - TestsUtil.generateTestUsers( - camAdminRestContext, - 6, - (err, users, user0, user1, user2, user3, user4, user5) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 6, (err, users, user0, user1, user2, user3, user4, user5) => { + assert.ok(!err); - const email1 = _emailForTenant(global.oaeTests.tenants.cam); - const email2 = _emailForTenant(global.oaeTests.tenants.cam); - const email3 = _emailForTenant(global.oaeTests.tenants.cam); - const email4 = _emailForTenant(global.oaeTests.tenants.cam); - const email5 = _emailForTenant(global.oaeTests.tenants.cam); - - fns.createSucceeds(user0.restContext, 'private', [email1], [], resource => { - // Accept as user1 - AuthzTestUtil.assertAcceptInvitationForEmailSucceeds(user1.restContext, email1, () => { - // Ensure user1 can now set roles since they should be manager - let roles = {}; - roles[email2] = 'manager'; - roles[email3] = memberRole; - roles[email4] = memberRole; - roles[email5] = memberRole; - fns.setRolesSucceeds(user0.restContext, user1.restContext, resource.id, roles, () => { - // Remove the user who invited email2 and ensure email2 invitation can still be accepted - roles = {}; - roles[user1.user.id] = false; - fns.setRolesSucceeds(user0.restContext, user0.restContext, resource.id, roles, () => { - // Ensure email2 can still be accepted and makes user2 a manager - AuthzTestUtil.assertAcceptInvitationForEmailSucceeds( - user2.restContext, - email2, - () => { + const email1 = _emailForTenant(global.oaeTests.tenants.cam); + const email2 = _emailForTenant(global.oaeTests.tenants.cam); + const email3 = _emailForTenant(global.oaeTests.tenants.cam); + const email4 = _emailForTenant(global.oaeTests.tenants.cam); + const email5 = _emailForTenant(global.oaeTests.tenants.cam); + + fns.createSucceeds(user0.restContext, 'private', [email1], [], resource => { + // Accept as user1 + AuthzTestUtil.assertAcceptInvitationForEmailSucceeds(user1.restContext, email1, () => { + // Ensure user1 can now set roles since they should be manager + let roles = {}; + roles[email2] = 'manager'; + roles[email3] = memberRole; + roles[email4] = memberRole; + roles[email5] = memberRole; + fns.setRolesSucceeds(user0.restContext, user1.restContext, resource.id, roles, () => { + // Remove the user who invited email2 and ensure email2 invitation can still be accepted + roles = {}; + roles[user1.user.id] = false; + fns.setRolesSucceeds(user0.restContext, user0.restContext, resource.id, roles, () => { + // Ensure email2 can still be accepted and makes user2 a manager + AuthzTestUtil.assertAcceptInvitationForEmailSucceeds(user2.restContext, email2, () => { + _assertRole(user0, user2, resource, { role: 'manager' }, () => { + // Accept the "member" role invitation for email3 as user2, ensuring their role + // on the resource does not get demoted to the "member" role + AuthzTestUtil.assertAcceptInvitationForEmailSucceeds(user2.restContext, email3, () => { _assertRole(user0, user2, resource, { role: 'manager' }, () => { - // Accept the "member" role invitation for email3 as user2, ensuring their role - // on the resource does not get demoted to the "member" role - AuthzTestUtil.assertAcceptInvitationForEmailSucceeds( - user2.restContext, - email3, + // Delete user1 from the system, and ensure the user they + // invited can still accept their invitation + PrincipalsTestUtil.assertDeleteUserSucceeds( + camAdminRestContext, + camAdminRestContext, + user1.user.id, () => { - _assertRole(user0, user2, resource, { role: 'manager' }, () => { - // Delete user1 from the system, and ensure the user they - // invited can still accept their invitation - PrincipalsTestUtil.assertDeleteUserSucceeds( - camAdminRestContext, - camAdminRestContext, - user1.user.id, - () => { - // Accept the invitation for email4 and ensure it succeeds despite the fact that - // the user that invited them was deleted - AuthzTestUtil.assertAcceptInvitationForEmailSucceeds( - user4.restContext, - email4, - () => { - _assertRole( - user0, - user4, - resource, - { role: memberRole }, - () => { - // Remove the invitation for email5, ensuring an invitation for email5 can still - // be accepted, but it doesn't grant any access to the resource - roles = {}; - roles[email5] = false; - fns.setRolesSucceeds( - user0.restContext, - user0.restContext, - resource.id, - roles, - () => { - AuthzTestUtil.assertAcceptInvitationForEmailSucceeds( - user5.restContext, - email5, - () => { - _assertRole( - user0, - user5, - resource, - { role: false }, - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + // Accept the invitation for email4 and ensure it succeeds despite the fact that + // the user that invited them was deleted + AuthzTestUtil.assertAcceptInvitationForEmailSucceeds(user4.restContext, email4, () => { + _assertRole(user0, user4, resource, { role: memberRole }, () => { + // Remove the invitation for email5, ensuring an invitation for email5 can still + // be accepted, but it doesn't grant any access to the resource + roles = {}; + roles[email5] = false; + fns.setRolesSucceeds(user0.restContext, user0.restContext, resource.id, roles, () => { + AuthzTestUtil.assertAcceptInvitationForEmailSucceeds(user5.restContext, email5, () => { + _assertRole(user0, user5, resource, { role: false }, () => { + return callback(); + }); + }); + }); + }); }); } ); }); - } - ); + }); + }); }); }); }); }); - } - ); + }); + }); }; /*! - * Ensure parameter validation for accepting an invitation - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure parameter validation for accepting an invitation + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationAcceptValidation = function(resourceType, callback) { const fns = resourceFns[resourceType]; - TestsUtil.generateTestUsers( - camAdminRestContext, - 3, - (err, users, user0, acceptingUser, sneakyUser) => { - assert.ok(!err); - - let email = _emailForTenant(global.oaeTests.tenants.cam); - - // Token is required - AuthzTestUtil.assertAcceptInvitationFails(sneakyUser.restContext, null, 400, () => { - // Token must exist - AuthzTestUtil.assertAcceptInvitationFails( - sneakyUser.restContext, - 'nonexistingtoken', - 404, - () => { - // Create a resource with an invitation - fns.createSucceeds(user0.restContext, 'public', [email], [], resource => { - // Lower-case the email now so we can work with the data layer, where the - // email should have been lower-cased - email = email.toLowerCase(); - - AuthzInvitationsDAO.getTokensByEmails([email], (err, tokensByEmail) => { - assert.ok(!err); - - const token = tokensByEmail[email]; + TestsUtil.generateTestUsers(camAdminRestContext, 3, (err, users, user0, acceptingUser, sneakyUser) => { + assert.ok(!err); - // User must be logged in to accept - AuthzTestUtil.assertAcceptInvitationFails( - anonymousRestContext, - token, - 401, - () => { - // Sanity check we can accept with this token as authenticated user - AuthzTestUtil.assertAcceptInvitationSucceeds( - acceptingUser.restContext, - token, - () => { - // Ensure re-accepting this token as a sneaky user that intercepted it fails - AuthzTestUtil.assertAcceptInvitationFails( - sneakyUser.restContext, - token, - 404, - () => { - // Ensure the accepting user became a manager of the resource - _assertRole( - user0, - acceptingUser, - resource, - { role: 'manager' }, - () => { - // Ensure the sneaky user does not have the resource - _assertRole( - user0, - sneakyUser, - resource, - { role: false, membersSize: 2 }, - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + let email = _emailForTenant(global.oaeTests.tenants.cam); + + // Token is required + AuthzTestUtil.assertAcceptInvitationFails(sneakyUser.restContext, null, 400, () => { + // Token must exist + AuthzTestUtil.assertAcceptInvitationFails(sneakyUser.restContext, 'nonexistingtoken', 404, () => { + // Create a resource with an invitation + fns.createSucceeds(user0.restContext, 'public', [email], [], resource => { + // Lower-case the email now so we can work with the data layer, where the + // email should have been lower-cased + email = email.toLowerCase(); + + AuthzInvitationsDAO.getTokensByEmails([email], (err, tokensByEmail) => { + assert.ok(!err); + + const token = tokensByEmail[email]; + + // User must be logged in to accept + AuthzTestUtil.assertAcceptInvitationFails(anonymousRestContext, token, 401, () => { + // Sanity check we can accept with this token as authenticated user + AuthzTestUtil.assertAcceptInvitationSucceeds(acceptingUser.restContext, token, () => { + // Ensure re-accepting this token as a sneaky user that intercepted it fails + AuthzTestUtil.assertAcceptInvitationFails(sneakyUser.restContext, token, 404, () => { + // Ensure the accepting user became a manager of the resource + _assertRole(user0, acceptingUser, resource, { role: 'manager' }, () => { + // Ensure the sneaky user does not have the resource + _assertRole(user0, sneakyUser, resource, { role: false, membersSize: 2 }, () => { + return callback(); + }); + }); + }); }); }); - } - ); + }); + }); }); - } - ); + }); + }); }; /*! - * Ensure that the invitations list is persisted appropriately with the expected roles when - * an invitation occurrs in the resource "create" request - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure that the invitations list is persisted appropriately with the expected roles when + * an invitation occurrs in the resource "create" request + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationsForCreate = function(resourceType, callback) { const fns = resourceFns[resourceType]; - TestsUtil.generateTestUsers( - camAdminRestContext, - 3, - (err, users, user0, userManager, userViewer) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 3, (err, users, user0, userManager, userViewer) => { + assert.ok(!err); - // Ensure a simple create mixed with a couple member users succeeds - fns.createSucceeds( - user0.restContext, - 'public', - ['manager@oae.local', userManager.user.id], - [userViewer.user.id, 'viewer@oae.local'], - // eslint-disable-next-line no-unused-vars - resource => { - return callback(); - } - ); - } - ); + // Ensure a simple create mixed with a couple member users succeeds + fns.createSucceeds( + user0.restContext, + 'public', + ['manager@oae.local', userManager.user.id], + [userViewer.user.id, 'viewer@oae.local'], + // eslint-disable-next-line no-unused-vars + resource => { + return callback(); + } + ); + }); }; /*! - * Ensure parameter validation of inviting "email-like" strings when an invitation is attempted - * in the resource "create" request - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure parameter validation of inviting "email-like" strings when an invitation is attempted + * in the resource "create" request + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationsValidationForCreate = function(resourceType, callback) { const fns = resourceFns[resourceType]; TestsUtil.generateTestUsers(camAdminRestContext, 1, (err, users, user0) => { @@ -2356,13 +2003,13 @@ describe('Invitations', () => { }; /*! - * Ensure authorization of inviting users from a variety of different types of tenants when - * inviting through the resource "create" action - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure authorization of inviting users from a variety of different types of tenants when + * inviting through the resource "create" action + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationsAuthorizationForCreate = function(resourceType, callback) { const fns = resourceFns[resourceType]; TestsUtil.setupMultiTenantPrivacyEntities((publicTenant0, publicTenant1, privateTenant0) => { @@ -2473,13 +2120,13 @@ describe('Invitations', () => { }; /*! - * Ensure that the invitations list is persisted appropriately with the expected roles when - * an invitation occurrs in the resource "share" request - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure that the invitations list is persisted appropriately with the expected roles when + * an invitation occurrs in the resource "share" request + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationsForShare = function(resourceType, callback) { const fns = resourceFns[resourceType]; TestsUtil.generateTestUsers(camAdminRestContext, 3, (err, users, userSharer, user0, user1) => { @@ -2501,98 +2148,61 @@ describe('Invitations', () => { }; /*! - * Ensure parameter validation of inviting "email-like" strings when an invitation is attempted - * in the resource "share" request - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure parameter validation of inviting "email-like" strings when an invitation is attempted + * in the resource "share" request + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationsValidationForShare = function(resourceType, callback) { const fns = resourceFns[resourceType]; TestsUtil.generateTestUsers(camAdminRestContext, 1, (err, users, user0) => { assert.ok(!err); fns.createSucceeds(user0.restContext, 'public', [], [], resource => { // Ensure cannot share with a variation of an email address - fns.shareFails( - user0.restContext, - user0.restContext, - resource.id, - ['email1@oae'], - 400, - () => { - // Sanity check share succeeds - fns.shareSucceeds( - user0.restContext, - user0.restContext, - resource.id, - ['email1@oae.local'], - () => { - return callback(); - } - ); - } - ); + fns.shareFails(user0.restContext, user0.restContext, resource.id, ['email1@oae'], 400, () => { + // Sanity check share succeeds + fns.shareSucceeds(user0.restContext, user0.restContext, resource.id, ['email1@oae.local'], () => { + return callback(); + }); + }); }); }); }; /*! - * Ensure authorization of inviting users from a variety of different types of tenants and - * resource visibilities when inviting through the resource "share" action - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure authorization of inviting users from a variety of different types of tenants and + * resource visibilities when inviting through the resource "share" action + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationsAuthorizationForShare = function(resourceType, callback) { const fns = resourceFns[resourceType]; TestsUtil.setupMultiTenantPrivacyEntities((publicTenant0, publicTenant1, privateTenant0) => { - _testInvitationsAuthorizationForPublicShare( - fns, - publicTenant0, - publicTenant1, - privateTenant0, - () => { - _testInvitationsAuthorizationForLoggedinShare( - fns, - publicTenant0, - publicTenant1, - privateTenant0, - () => { - _testInvitationsAuthorizationForPrivateShare( - fns, - publicTenant0, - publicTenant1, - privateTenant0, - () => { - _testInvitationsAuthorizationForNoGuestsShare( - fns, - publicTenant0, - publicTenant1, - privateTenant0, - callback - ); - } - ); - } - ); - } - ); + _testInvitationsAuthorizationForPublicShare(fns, publicTenant0, publicTenant1, privateTenant0, () => { + _testInvitationsAuthorizationForLoggedinShare(fns, publicTenant0, publicTenant1, privateTenant0, () => { + _testInvitationsAuthorizationForPrivateShare(fns, publicTenant0, publicTenant1, privateTenant0, () => { + _testInvitationsAuthorizationForNoGuestsShare(fns, publicTenant0, publicTenant1, privateTenant0, callback); + }); + }); + }); }); }; /*! - * Ensure the authorization constraints of sharing a resource with emails from a variety - * of different types of tenants are as expected - * - * @param {Object} fns The functions specification for the resource type to test, as given in `resourceFns` - * @param {Object} publicTenant0 The tenant info of a public tenant - * @param {Object} publicTenant1 The tenant info of another public tenant - * @param {Object} privateTenant0 The tenant info of a private tenant - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure the authorization constraints of sharing a resource with emails from a variety + * of different types of tenants are as expected + * + * @param {Object} fns The functions specification for the resource type to test, as given in `resourceFns` + * @param {Object} publicTenant0 The tenant info of a public tenant + * @param {Object} publicTenant1 The tenant info of another public tenant + * @param {Object} privateTenant0 The tenant info of a private tenant + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationsAuthorizationForPublicShare = function( fns, publicTenant0, @@ -2677,16 +2287,16 @@ describe('Invitations', () => { }; /*! - * Ensure the authorization constraints of sharing a loggedin resource with emails from a variety - * of different types of tenants are as expected - * - * @param {Object} fns The functions specification for the resource type to test, as given in `resourceFns` - * @param {Object} publicTenant0 The tenant info of a public tenant - * @param {Object} publicTenant1 The tenant info of another public tenant - * @param {Object} privateTenant0 The tenant info of a private tenant - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure the authorization constraints of sharing a loggedin resource with emails from a variety + * of different types of tenants are as expected + * + * @param {Object} fns The functions specification for the resource type to test, as given in `resourceFns` + * @param {Object} publicTenant0 The tenant info of a public tenant + * @param {Object} publicTenant1 The tenant info of another public tenant + * @param {Object} privateTenant0 The tenant info of a private tenant + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationsAuthorizationForLoggedinShare = function( fns, publicTenant0, @@ -2773,16 +2383,16 @@ describe('Invitations', () => { }; /*! - * Ensure the authorization constraints of sharing a private resource with emails from a variety - * of different types of tenants are as expected - * - * @param {Object} fns The functions specification for the resource type to test, as given in `resourceFns` - * @param {Object} publicTenant0 The tenant info of a public tenant - * @param {Object} publicTenant1 The tenant info of another public tenant - * @param {Object} privateTenant0 The tenant info of a private tenant - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure the authorization constraints of sharing a private resource with emails from a variety + * of different types of tenants are as expected + * + * @param {Object} fns The functions specification for the resource type to test, as given in `resourceFns` + * @param {Object} publicTenant0 The tenant info of a public tenant + * @param {Object} publicTenant1 The tenant info of another public tenant + * @param {Object} privateTenant0 The tenant info of a private tenant + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationsAuthorizationForPrivateShare = function( fns, publicTenant0, @@ -2870,16 +2480,16 @@ describe('Invitations', () => { }; /*! - * Ensure the authorization constraints of sharing a resource with guests on - * a tenant that has disabled inviting guests - * - * @param {Object} fns The functions specification for the resource type to test, as given in `resourceFns` - * @param {Object} publicTenant0 The tenant info of a public tenant - * @param {Object} publicTenant1 The tenant info of another public tenant - * @param {Object} privateTenant0 The tenant info of a private tenant - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure the authorization constraints of sharing a resource with guests on + * a tenant that has disabled inviting guests + * + * @param {Object} fns The functions specification for the resource type to test, as given in `resourceFns` + * @param {Object} publicTenant0 The tenant info of a public tenant + * @param {Object} publicTenant1 The tenant info of another public tenant + * @param {Object} privateTenant0 The tenant info of a private tenant + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationsAuthorizationForNoGuestsShare = function( fns, publicTenant0, @@ -2968,71 +2578,49 @@ describe('Invitations', () => { }; /*! - * Ensure that the invitations list is persisted appropriately with the expected roles when - * an invitation occurrs in the resource "set roles" request - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure that the invitations list is persisted appropriately with the expected roles when + * an invitation occurrs in the resource "set roles" request + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationsForSetRoles = function(resourceType, callback) { const fns = resourceFns[resourceType]; const memberRole = resourceMemberRoles[resourceType]; - TestsUtil.generateTestUsers( - camAdminRestContext, - 3, - (err, users, userSetRoles, user0, user1) => { - assert.ok(!err); - fns.createSucceeds( - userSetRoles.restContext, - 'public', - ['email1@oae.local'], - [], - resource => { - // Ensure a simple set roles works as expected. email1 should be demoted to the - // member role, and email2 should be added as a manager - const roles = {}; - roles[user0.user.id] = 'manager'; - roles[user1.user.id] = memberRole; - roles['email1@oae.local'] = memberRole; - roles['email2@oae.local'] = 'manager'; - - // Set the roles for both members and invitations - fns.setRolesSucceeds( - userSetRoles.restContext, - userSetRoles.restContext, - resource.id, - roles, - () => { - // Now remove them all, ensuring the states are updated appropriately - const rolesRemove = AuthzTestUtil.createRoleChange(_.keys(roles), false); - fns.setRolesSucceeds( - userSetRoles.restContext, - userSetRoles.restContext, - resource.id, - rolesRemove, - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); + TestsUtil.generateTestUsers(camAdminRestContext, 3, (err, users, userSetRoles, user0, user1) => { + assert.ok(!err); + fns.createSucceeds(userSetRoles.restContext, 'public', ['email1@oae.local'], [], resource => { + // Ensure a simple set roles works as expected. email1 should be demoted to the + // member role, and email2 should be added as a manager + const roles = {}; + roles[user0.user.id] = 'manager'; + roles[user1.user.id] = memberRole; + roles['email1@oae.local'] = memberRole; + roles['email2@oae.local'] = 'manager'; + + // Set the roles for both members and invitations + fns.setRolesSucceeds(userSetRoles.restContext, userSetRoles.restContext, resource.id, roles, () => { + // Now remove them all, ensuring the states are updated appropriately + const rolesRemove = AuthzTestUtil.createRoleChange(_.keys(roles), false); + fns.setRolesSucceeds(userSetRoles.restContext, userSetRoles.restContext, resource.id, rolesRemove, () => { + return callback(); + }); + }); + }); + }); }; /*! - * Ensure that when an email that has multiple resource invitations has one - * resource removed, the other resources are still accepted when the - * invitation is accepted - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure that when an email that has multiple resource invitations has one + * resource removed, the other resources are still accepted when the + * invitation is accepted + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationsPartialRemoveRoles = function(resourceType, callback) { const fns = resourceFns[resourceType]; const memberRole = resourceMemberRoles[resourceType]; @@ -3047,86 +2635,67 @@ describe('Invitations', () => { fns.createSucceeds(userSetRoles.restContext, 'public', [], [email], resource2 => { // Remove the email from one of the resources const roles = _.oaeObj(email, false); - fns.setRolesSucceeds( - userSetRoles.restContext, - userSetRoles.restContext, - resource1.id, - roles, - () => { - // Accept the email invitation as the invited email user, ensuring they get access - // to just the one resource (resource2) - const assertions = { role: memberRole, membersSize: 2, librarySize: 1 }; - _assertAcceptEmailInvitation( - userSetRoles, - userAccept, - [resource2], - assertions, - () => { - return callback(); - } - ); - } - ); + fns.setRolesSucceeds(userSetRoles.restContext, userSetRoles.restContext, resource1.id, roles, () => { + // Accept the email invitation as the invited email user, ensuring they get access + // to just the one resource (resource2) + const assertions = { role: memberRole, membersSize: 2, librarySize: 1 }; + _assertAcceptEmailInvitation(userSetRoles, userAccept, [resource2], assertions, () => { + return callback(); + }); + }); }); }); }); }; /*! - * Ensure parameter validation of inviting "email-like" strings when an invitation is attempted - * in the resource "set roles" request - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure parameter validation of inviting "email-like" strings when an invitation is attempted + * in the resource "set roles" request + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationsValidationForSetRoles = function(resourceType, callback) { const fns = resourceFns[resourceType]; TestsUtil.generateTestUsers(camAdminRestContext, 1, (err, users, user0) => { assert.ok(!err); fns.createSucceeds(user0.restContext, 'public', [], [], resource => { // Ensure invalid email is rejected - fns.setRolesFails( - user0.restContext, - user0.restContext, - resource.id, - { 'email1@oae': 'manager' }, - 400, - () => { - // Ensure invalid role for email is rejected - fns.setRolesFails( - user0.restContext, - user0.restContext, - resource.id, - { 'email1@oae.local': 'invalidrole' }, - 400, - () => { - // Sanity check we can set roles with a valid role - fns.setRolesSucceeds( - user0.restContext, - user0.restContext, - resource.id, - { 'email1@oae.local': 'manager' }, - () => { - return callback(); - } - ); - } - ); - } - ); + fns.setRolesFails(user0.restContext, user0.restContext, resource.id, { 'email1@oae': 'manager' }, 400, () => { + // Ensure invalid role for email is rejected + fns.setRolesFails( + user0.restContext, + user0.restContext, + resource.id, + { 'email1@oae.local': 'invalidrole' }, + 400, + () => { + // Sanity check we can set roles with a valid role + fns.setRolesSucceeds( + user0.restContext, + user0.restContext, + resource.id, + { 'email1@oae.local': 'manager' }, + () => { + return callback(); + } + ); + } + ); + }); }); }); }; /*! - * Ensure authorization of inviting users from a variety of different types of tenants when - * inviting through the resource "set roles" action - * - * @param {String} resourceType The resource type for which to execute the test - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure authorization of inviting users from a variety of different types of tenants when + * inviting through the resource "set roles" action + * + * @param {String} resourceType The resource type for which to execute the test + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _testInvitationsAuthorizationForSetRoles = function(resourceType, callback) { const fns = resourceFns[resourceType]; const memberRole = resourceMemberRoles[resourceType]; @@ -3155,78 +2724,72 @@ describe('Invitations', () => { rolesExternalPrivateTenant[_emailForTenantInfo(privateTenant0)] = memberRole; // Ensure manager can set invitation roles for all emails they can interact with - fns.setRolesSucceeds( - managerUser.restContext, - managerUser.restContext, - resource.id, - rolesSameTenant, - () => { - fns.setRolesSucceeds( - managerUser.restContext, - managerUser.restContext, - resource.id, - rolesExternalPublicTenant, - () => { - fns.setRolesSucceeds( - managerUser.restContext, - managerUser.restContext, - resource.id, - rolesGuestTenant, - () => { - fns.setRolesFails( - managerUser.restContext, - managerUser.restContext, - resource.id, - rolesExternalPrivateTenant, - 401, - () => { - // Ensure a user on a tenant that has disabled inviting guests - // cannot invite guests that end up on the guest tenant - _disableInvitingGuests(publicTenant0.tenant.alias, () => { - fns.setRolesSucceeds( - managerUser.restContext, - managerUser.restContext, - resource.id, - rolesSameTenant, - () => { - fns.setRolesSucceeds( - managerUser.restContext, - managerUser.restContext, - resource.id, - rolesExternalPublicTenant, - () => { - fns.setRolesFails( - managerUser.restContext, - managerUser.restContext, - resource.id, - rolesGuestTenant, - 401, - () => { - fns.setRolesFails( - managerUser.restContext, - managerUser.restContext, - resource.id, - rolesExternalPrivateTenant, - 401, - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); - }); - } - ); - } - ); - } - ); - } - ); + fns.setRolesSucceeds(managerUser.restContext, managerUser.restContext, resource.id, rolesSameTenant, () => { + fns.setRolesSucceeds( + managerUser.restContext, + managerUser.restContext, + resource.id, + rolesExternalPublicTenant, + () => { + fns.setRolesSucceeds( + managerUser.restContext, + managerUser.restContext, + resource.id, + rolesGuestTenant, + () => { + fns.setRolesFails( + managerUser.restContext, + managerUser.restContext, + resource.id, + rolesExternalPrivateTenant, + 401, + () => { + // Ensure a user on a tenant that has disabled inviting guests + // cannot invite guests that end up on the guest tenant + _disableInvitingGuests(publicTenant0.tenant.alias, () => { + fns.setRolesSucceeds( + managerUser.restContext, + managerUser.restContext, + resource.id, + rolesSameTenant, + () => { + fns.setRolesSucceeds( + managerUser.restContext, + managerUser.restContext, + resource.id, + rolesExternalPublicTenant, + () => { + fns.setRolesFails( + managerUser.restContext, + managerUser.restContext, + resource.id, + rolesGuestTenant, + 401, + () => { + fns.setRolesFails( + managerUser.restContext, + managerUser.restContext, + resource.id, + rolesExternalPrivateTenant, + 401, + () => { + return callback(); + } + ); + } + ); + } + ); + } + ); + }); + } + ); + } + ); + } + ); + }); } ); }); @@ -3234,12 +2797,12 @@ describe('Invitations', () => { }; /*! - * Disable inviting guests for a given tenant - * - * @param {String} tenantAlias The alias of the tenant to disable the inviting guests feature for - * @param {Function} callback Invoked when the configuration has been updated - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Disable inviting guests for a given tenant + * + * @param {String} tenantAlias The alias of the tenant to disable the inviting guests feature for + * @param {Function} callback Invoked when the configuration has been updated + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _disableInvitingGuests = function(tenantAlias, callback) { ConfigTestUtil.updateConfigAndWait( globalAdminRestContext, @@ -3278,12 +2841,7 @@ describe('Invitations', () => { contextId: _.first(resources).id }); - _assertStandardInvitationAcceptSummaries( - inviterUserInfo, - invitedUserInfo, - otherUserInfo, - resources - ); + _assertStandardInvitationAcceptSummaries(inviterUserInfo, invitedUserInfo, otherUserInfo, resources); // Check the resource-specific summary match against this number of resources const match = assertions.matches[resources.length - 1]; @@ -3382,35 +2940,29 @@ describe('Invitations', () => { }; /*! - * Collect pending emails, ensure the following: - * - * * Email Contents: Ensure the contents of the email contains each of the specified - * resources - * * Invitation Link: Ensure the invitation link is present and contains a token that allows - * the email recipient to accept the invitation, gaining access to the - * specified resources - * * Accepting: Ensure that when the email recipient accepts the invitation, they are - * given all the specified resources in their respective libraries feeds - * and searches. Also, it ensures that the members feed of the accepted - * resource contains the user who accepted the invitation - * - * @param {Object} invitingUserInfo The user info of the user who performed the invitation - * @param {Object} acceptingUserInfo The user info of the user who should accept the invitation - * @param {Resource[]} resources The resources we expect to be accepted in this email invitation - * @param {Object} assertions The assertion data according to the context o the data setup - * @param {String} assertions.role The role, if any, we expect the user to have on each resource after accepting - * @param {Number} assertions.membersSize The expected size of the resource members libraries after accepting - * @param {Number} assertions.librarySize The expected size of the respective resource library after accepting - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ - const _assertAcceptEmailInvitation = function( - invitingUserInfo, - acceptingUserInfo, - resources, - assertions, - callback - ) { + * Collect pending emails, ensure the following: + * + * * Email Contents: Ensure the contents of the email contains each of the specified + * resources + * * Invitation Link: Ensure the invitation link is present and contains a token that allows + * the email recipient to accept the invitation, gaining access to the + * specified resources + * * Accepting: Ensure that when the email recipient accepts the invitation, they are + * given all the specified resources in their respective libraries feeds + * and searches. Also, it ensures that the members feed of the accepted + * resource contains the user who accepted the invitation + * + * @param {Object} invitingUserInfo The user info of the user who performed the invitation + * @param {Object} acceptingUserInfo The user info of the user who should accept the invitation + * @param {Resource[]} resources The resources we expect to be accepted in this email invitation + * @param {Object} assertions The assertion data according to the context o the data setup + * @param {String} assertions.role The role, if any, we expect the user to have on each resource after accepting + * @param {Number} assertions.membersSize The expected size of the resource members libraries after accepting + * @param {Number} assertions.librarySize The expected size of the respective resource library after accepting + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ + const _assertAcceptEmailInvitation = function(invitingUserInfo, acceptingUserInfo, resources, assertions, callback) { // Receive the email invitation, ensuring we only have 1 EmailTestUtil.collectAndFetchAllEmails(messages => { assert.strictEqual(_.size(messages), 1); @@ -3432,8 +2984,8 @@ describe('Invitations', () => { assert.ok(hasOne); // Ensure the token in the email is functional - const token = url.parse(AuthzTestUtil.parseInvitationUrlFromMessage(message).query.url, true) - .query.invitationToken; + const token = url.parse(AuthzTestUtil.parseInvitationUrlFromMessage(message).query.url, true).query + .invitationToken; AuthzTestUtil.assertAcceptInvitationSucceeds(acceptingUserInfo.restContext, token, () => { // Ensure the user has the specified role on all the resources // eslint-disable-next-line no-unused-vars @@ -3453,28 +3005,29 @@ describe('Invitations', () => { }; /*! - * Ensure the potential member user has the given role (if any) on all the specified resources - * including: - * - * * Members Feed: Ensure the members feed of the resource contains the resource - * * Library Feed: Ensure the respective library feed of the user contains each resource - * * Library Search: Ensure the respective library search of the user contains each resource - * - * @param {Object} managerUserInfo The user info of a user who manages each resource - * @param {Object} memberUserInfo The user info of the user we are going to test for membership - * @param {Resource[]} resources The resources we are checking against for membership - * @param {Object} assertions The assertion data according to the context o the data setup - * @param {String} assertions.role The role, if any, we expect the user to have on each resource - * @param {Number} assertions.membersSize The expected size of the resource members libraries - * @param {Number} assertions.librarySize The expected size of the respective resource library - * @param {Function} callback Invoked when the test is complete - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure the potential member user has the given role (if any) on all the specified resources + * including: + * + * * Members Feed: Ensure the members feed of the resource contains the resource + * * Library Feed: Ensure the respective library feed of the user contains each resource + * * Library Search: Ensure the respective library search of the user contains each resource + * + * @param {Object} managerUserInfo The user info of a user who manages each resource + * @param {Object} memberUserInfo The user info of the user we are going to test for membership + * @param {Resource[]} resources The resources we are checking against for membership + * @param {Object} assertions The assertion data according to the context o the data setup + * @param {String} assertions.role The role, if any, we expect the user to have on each resource + * @param {Number} assertions.membersSize The expected size of the resource members libraries + * @param {Number} assertions.librarySize The expected size of the respective resource library + * @param {Function} callback Invoked when the test is complete + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _assertRole = function(managerUserInfo, memberUserInfo, resources, assertions, callback) { assertions = assertions || {}; if (!_.isArray(resources)) { return _assertRole(managerUserInfo, memberUserInfo, [resources], assertions, callback); } + if (_.isEmpty(resources)) { return callback(); } @@ -3521,53 +3074,46 @@ describe('Invitations', () => { : SearchTestUtil.assertSearchNotContains; const libraryName = resourceLibraryInfo[resource.resourceType]; - searchAssertFn( - memberUserInfo.restContext, - libraryName, - [memberUserInfo.user.id], - null, - [resource.id], - () => { - return _assertRole(managerUserInfo, memberUserInfo, resources, assertions, callback); - } - ); + searchAssertFn(memberUserInfo.restContext, libraryName, [memberUserInfo.user.id], null, [resource.id], () => { + return _assertRole(managerUserInfo, memberUserInfo, resources, assertions, callback); + }); }); }); }; /*! - * Ensure that the specified invitation email HTML indicates the specified resource without its - * profile path. It should not have its profile path because it is not a link. - * - * @param {String} html The html content to check - * @param {Resource} resource The resource to ensure is present in the html content - * @throws {AssertionError} Thrown if the conditions are not met - */ + * Ensure that the specified invitation email HTML indicates the specified resource without its + * profile path. It should not have its profile path because it is not a link. + * + * @param {String} html The html content to check + * @param {Resource} resource The resource to ensure is present in the html content + * @throws {AssertionError} Thrown if the conditions are not met + */ const _assertInvitationContainsResourceHtml = function(html, resource) { _assertContains(html, resource.displayName); _assertNotContains(html, resource.profilePath); }; /*! - * Ensure that the specified accepted invitation activity email HTML indicates the specified - * resource - * - * @param {String} html The html content to check - * @param {Resource} resource The resource to ensure is present in the html content - * @throws {AssertionError} Thrown if the conditions are not met - */ + * Ensure that the specified accepted invitation activity email HTML indicates the specified + * resource + * + * @param {String} html The html content to check + * @param {Resource} resource The resource to ensure is present in the html content + * @throws {AssertionError} Thrown if the conditions are not met + */ const _assertAcceptInvitationContainsResourceHtml = function(html, resource) { _assertContains(html, resource.displayName); _assertContains(html, resource.profilePath); }; /*! - * Ensure the source string contains the match string - * - * @param {String} sourceStr The source string to match - * @param {String} matchStr The string to ensure is present in the source string - * @throws {AssertionError} Thrown if the conditions are not met - */ + * Ensure the source string contains the match string + * + * @param {String} sourceStr The source string to match + * @param {String} matchStr The string to ensure is present in the source string + * @throws {AssertionError} Thrown if the conditions are not met + */ const _assertContains = function(sourceStr, matchStr) { if (!matchStr) { assert.fail('Cannot assert against a falsey string'); @@ -3577,12 +3123,12 @@ describe('Invitations', () => { }; /*! - * Ensure the source string does not contain the match string - * - * @param {String} sourceStr The source string to match - * @param {String} matchStr The string to ensure is not present in the source string - * @throws {AssertionError} Thrown if the conditions are not met - */ + * Ensure the source string does not contain the match string + * + * @param {String} sourceStr The source string to match + * @param {String} matchStr The string to ensure is not present in the source string + * @throws {AssertionError} Thrown if the conditions are not met + */ const _assertNotContains = function(sourceStr, matchStr) { if (!matchStr) { assert.fail('Cannot assert against a falsey string'); @@ -3592,23 +3138,17 @@ describe('Invitations', () => { }; /*! - * Convenience function to create one of each type of resource with the specified access - * - * @param {Object} creatingUserInfo The user info to use to create each resource - * @param {String} visibility The visibility to apply to each resource - * @param {String[]} managerIds The managers of the resource - * @param {String[]} memberIds The members of the resource - * @param {Function} callback Invoked when the test is complete - * @param {Resource[]} callback.resources The created resources - * @throws {AssertionError} Thrown if any of the assertions fail - */ - const _createOneOfEachResourceType = function( - creatingUserInfo, - visibility, - managerIds, - memberIds, - callback - ) { + * Convenience function to create one of each type of resource with the specified access + * + * @param {Object} creatingUserInfo The user info to use to create each resource + * @param {String} visibility The visibility to apply to each resource + * @param {String[]} managerIds The managers of the resource + * @param {String[]} memberIds The members of the resource + * @param {Function} callback Invoked when the test is complete + * @param {Resource[]} callback.resources The created resources + * @throws {AssertionError} Thrown if any of the assertions fail + */ + const _createOneOfEachResourceType = function(creatingUserInfo, visibility, managerIds, memberIds, callback) { // Create a resource of each known type, aggregating them into the `createResults` // object const resources = []; @@ -3624,30 +3164,24 @@ describe('Invitations', () => { // eslint-disable-next-line no-unused-vars PrincipalsTestUtil.assertGetMeSucceeds(creatingUserInfo.restContext, me => { _.each(resourceFns, fns => { - fns.createSucceeds( - creatingUserInfo.restContext, - visibility, - managerIds, - memberIds, - resource => { - resources.push(resource); - return _done(); - } - ); + fns.createSucceeds(creatingUserInfo.restContext, visibility, managerIds, memberIds, resource => { + resources.push(resource); + return _done(); + }); }); }); }; /*! - * Convenience function to extend the specified user info with the adapted preview items and - * summary of the specified activity - * - * @param {Object} userInfo The user info to extend - * @param {Activity} activity The activity whose adapted info to overlay - * @param {Object} [opts] Optional arguments - * @param {String} [opts.contextId] The context id for the adapted activity. Defaults to the user id of the user info - * @return {Object} The user info with the adapted activity preview items and summary applied - */ + * Convenience function to extend the specified user info with the adapted preview items and + * summary of the specified activity + * + * @param {Object} userInfo The user info to extend + * @param {Activity} activity The activity whose adapted info to overlay + * @param {Object} [opts] Optional arguments + * @param {String} [opts.contextId] The context id for the adapted activity. Defaults to the user id of the user info + * @return {Object} The user info with the adapted activity preview items and summary applied + */ const _withAdaptedInfo = function(userInfo, activity, opts) { opts = opts || {}; opts.contextId = opts.contextId || userInfo.user.id; @@ -3673,34 +3207,34 @@ describe('Invitations', () => { }; /*! - * Create an email whose domain matches that of the specified tenant info - * - * @param {Object} tenantInfo The tenant info object - * @param {String} [username] The username of the email. One will be randomly generated if unspecified - * @return {String} The created email - */ + * Create an email whose domain matches that of the specified tenant info + * + * @param {Object} tenantInfo The tenant info object + * @param {String} [username] The username of the email. One will be randomly generated if unspecified + * @return {String} The created email + */ const _emailForTenantInfo = function(tenantInfo, username) { return _emailForTenant(tenantInfo.tenant, username); }; /*! - * Create an email whose domain matches that of the specified tenant - * - * @param {Tenant} tenant The tenant - * @param {String} [username] The username of the email. One will be randomly generated if unspecified - * @return {String} The created email - */ + * Create an email whose domain matches that of the specified tenant + * + * @param {Tenant} tenant The tenant + * @param {String} [username] The username of the email. One will be randomly generated if unspecified + * @return {String} The created email + */ const _emailForTenant = function(tenant, username) { return _emailForDomain(tenant.emailDomains[0], username); }; /*! - * Create an email with the specified host and username - * - * @param {String} host The host - * @param {String} [username] The username of the email. One will be randomly generated if unspecified - * @return {String} The created email - */ + * Create an email with the specified host and username + * + * @param {String} host The host + * @param {String} [username] The username of the email. One will be randomly generated if unspecified + * @return {String} The created email + */ const _emailForDomain = function(host, username) { return util.format('%s@%s', username || TestsUtil.generateTestUserId(), host); }; diff --git a/packages/oae-authz/tests/test-permissions.js b/packages/oae-authz/tests/test-permissions.js index dddb782831..bb99ddc263 100644 --- a/packages/oae-authz/tests/test-permissions.js +++ b/packages/oae-authz/tests/test-permissions.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const AuthzAPI = require('oae-authz'); -const AuthzUtil = require('oae-authz/lib/util'); +import assert from 'assert'; +import * as AuthzAPI from 'oae-authz'; +import * as AuthzUtil from 'oae-authz/lib/util'; describe('Authz-Permissions', () => { /** diff --git a/packages/oae-authz/tests/test-roles.js b/packages/oae-authz/tests/test-roles.js index ae961cb868..2d7f1c18d8 100644 --- a/packages/oae-authz/tests/test-roles.js +++ b/packages/oae-authz/tests/test-roles.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); -const AuthzAPI = require('oae-authz'); -const AuthzUtil = require('oae-authz/lib/util'); +import assert from 'assert'; +import _ from 'underscore'; +import * as AuthzAPI from 'oae-authz'; +import * as AuthzUtil from 'oae-authz/lib/util'; describe('Authz-Roles', () => { const PrincipalTypes = { USER: 'u', GROUP: 'g' }; @@ -32,37 +32,20 @@ describe('Authz-Roles', () => { * @param {String} role The role to assign to the principal on the generated content * @param {Function()} callback The function invoked when the process is complete */ - const loadContentRoles = function( - principalId, - baseContentId, - resourceType, - numContentItems, - role, - callback - ) { + const loadContentRoles = function(principalId, baseContentId, resourceType, numContentItems, role, callback) { if (numContentItems === 0) { callback(); return; } const { tenantAlias } = AuthzUtil.getPrincipalFromId(principalId); - const resourceId = AuthzUtil.toId( - resourceType, - tenantAlias, - baseContentId + '-' + numContentItems - ); + const resourceId = AuthzUtil.toId(resourceType, tenantAlias, baseContentId + '-' + numContentItems); AuthzAPI.updateRoles(resourceId, makeChange(principalId, role), err => { if (err) { throw err; } - loadContentRoles( - principalId, - baseContentId, - resourceType, - numContentItems - 1, - role, - callback - ); + + loadContentRoles(principalId, baseContentId, resourceType, numContentItems - 1, role, callback); }); }; @@ -240,11 +223,7 @@ describe('Authz-Roles', () => { it('verify role separation between tenants', callback => { const principalIdA = AuthzUtil.toId(PrincipalTypes.USER, 'testTenantSeparationA', 'mrvisser'); const principalIdB = AuthzUtil.toId(PrincipalTypes.USER, 'testTenantSeparationB', 'mrvisser'); - const resourceId = AuthzUtil.toId( - ResourceTypes.CONTENT, - 'cam', - 'testTenantSeparationContent' - ); + const resourceId = AuthzUtil.toId(ResourceTypes.CONTENT, 'cam', 'testTenantSeparationContent'); AuthzAPI.updateRoles(resourceId, makeChange(principalIdA, 'manager'), err => { assert.ok(!err); @@ -359,21 +338,9 @@ describe('Authz-Roles', () => { const principalId2 = AuthzUtil.toId(PrincipalTypes.USER, 'testHasRole', 'nm417'); const principalId3 = AuthzUtil.toId(PrincipalTypes.USER, 'testHasRole', 'simong'); const principalId4 = AuthzUtil.toId(PrincipalTypes.USER, 'testHasRole', 'PhysX'); - const resourceId1 = AuthzUtil.toId( - ResourceTypes.CONTENT, - 'testHasRole', - 'testHasRoleContent1' - ); - const resourceId2 = AuthzUtil.toId( - ResourceTypes.CONTENT, - 'testHasRole', - 'testHasRoleContent2' - ); - const resourceId3 = AuthzUtil.toId( - ResourceTypes.CONTENT, - 'testHasRole', - 'testHasRoleContent3' - ); + const resourceId1 = AuthzUtil.toId(ResourceTypes.CONTENT, 'testHasRole', 'testHasRoleContent1'); + const resourceId2 = AuthzUtil.toId(ResourceTypes.CONTENT, 'testHasRole', 'testHasRoleContent2'); + const resourceId3 = AuthzUtil.toId(ResourceTypes.CONTENT, 'testHasRole', 'testHasRoleContent3'); // Make 1 user a manager let roles = {}; @@ -425,61 +392,36 @@ describe('Authz-Roles', () => { roles[principalId3] = false; AuthzAPI.updateRoles(resourceId3, roles, err => { assert.ok(!err); - AuthzAPI.hasRole( - principalId1, - resourceId3, - 'manager', - (err, hasRole) => { + AuthzAPI.hasRole(principalId1, resourceId3, 'manager', (err, hasRole) => { + assert.ok(!err); + assert.ok(hasRole); + AuthzAPI.hasRole(principalId3, resourceId3, 'member', (err, hasRole) => { assert.ok(!err); - assert.ok(hasRole); - AuthzAPI.hasRole( - principalId3, - resourceId3, - 'member', - (err, hasRole) => { + assert.ok(!hasRole); + + // Try to remove 2 roles and add 1 at the same time + roles = {}; + roles[principalId1] = false; + roles[principalId2] = false; + roles[principalId3] = 'manager'; + AuthzAPI.updateRoles(resourceId3, roles, err => { + assert.ok(!err); + AuthzAPI.hasRole(principalId1, resourceId3, 'manager', (err, hasRole) => { assert.ok(!err); assert.ok(!hasRole); - - // Try to remove 2 roles and add 1 at the same time - roles = {}; - roles[principalId1] = false; - roles[principalId2] = false; - roles[principalId3] = 'manager'; - AuthzAPI.updateRoles(resourceId3, roles, err => { + AuthzAPI.hasRole(principalId2, resourceId3, 'member', (err, hasRole) => { assert.ok(!err); - AuthzAPI.hasRole( - principalId1, - resourceId3, - 'manager', - (err, hasRole) => { - assert.ok(!err); - assert.ok(!hasRole); - AuthzAPI.hasRole( - principalId2, - resourceId3, - 'member', - (err, hasRole) => { - assert.ok(!err); - assert.ok(!hasRole); - AuthzAPI.hasRole( - principalId3, - resourceId3, - 'manager', - (err, hasRole) => { - assert.ok(!err); - assert.ok(hasRole); - callback(); - } - ); - } - ); - } - ); + assert.ok(!hasRole); + AuthzAPI.hasRole(principalId3, resourceId3, 'manager', (err, hasRole) => { + assert.ok(!err); + assert.ok(hasRole); + callback(); + }); }); - } - ); - } - ); + }); + }); + }); + }); }); }); }); @@ -497,11 +439,7 @@ describe('Authz-Roles', () => { it('verify validation', callback => { const principalId1 = AuthzUtil.toId(PrincipalTypes.USER, 'testHasRole', 'mrvisser'); const principalId2 = AuthzUtil.toId(PrincipalTypes.USER, 'testHasRole', 'nm417'); - const resourceId1 = AuthzUtil.toId( - ResourceTypes.CONTENT, - 'testHasRole', - 'testHasRoleContent1' - ); + const resourceId1 = AuthzUtil.toId(ResourceTypes.CONTENT, 'testHasRole', 'testHasRoleContent1'); const roles = {}; roles[principalId1] = 'manager'; @@ -522,122 +460,77 @@ describe('Authz-Roles', () => { it('verify general functionality', callback => { const baseViewerContentId = 'contentIView'; const baseManagerContentId = 'contentIManage'; - const principalId1 = AuthzUtil.toId( - PrincipalTypes.USER, - 'testGetRolesForPrincipalsAndResourceType', - 'mrvisser' - ); - const principalId2 = AuthzUtil.toId( - PrincipalTypes.GROUP, - 'testGetRolesForPrincipalsAndResourceType', - 'simong' - ); + const principalId1 = AuthzUtil.toId(PrincipalTypes.USER, 'testGetRolesForPrincipalsAndResourceType', 'mrvisser'); + const principalId2 = AuthzUtil.toId(PrincipalTypes.GROUP, 'testGetRolesForPrincipalsAndResourceType', 'simong'); // Mrvisser has 'viewer' role on a bunch of groups - loadContentRoles( - principalId1, - baseViewerContentId, - ResourceTypes.CONTENT, - 300, - 'viewer', - () => { - // Simong has 'manager' role on some of the groups that mrvisser has 'viewer' on. this is to test aggregation of roles - loadContentRoles( - principalId2, - baseViewerContentId, - ResourceTypes.CONTENT, - 50, - 'manager', - () => { - // Simong has 'manager' role on a bunch of groups - loadContentRoles( - principalId2, - baseManagerContentId, - ResourceTypes.CONTENT, - 300, - 'manager', - () => { - // Make sure they work together + loadContentRoles(principalId1, baseViewerContentId, ResourceTypes.CONTENT, 300, 'viewer', () => { + // Simong has 'manager' role on some of the groups that mrvisser has 'viewer' on. this is to test aggregation of roles + loadContentRoles(principalId2, baseViewerContentId, ResourceTypes.CONTENT, 50, 'manager', () => { + // Simong has 'manager' role on a bunch of groups + loadContentRoles(principalId2, baseManagerContentId, ResourceTypes.CONTENT, 300, 'manager', () => { + // Make sure they work together + AuthzAPI.getRolesForPrincipalsAndResourceType( + [principalId1, principalId2], + ResourceTypes.CONTENT, + (err, entries) => { + assert.ok(!err); + + // Simong is a member of 350, mrvisser is a member of 300, but 50 of those overlap, so should be 600 unique entries + assert.strictEqual(_.keys(entries).length, 2); + assert.strictEqual(_.keys(entries[principalId1]).length, 300); + assert.strictEqual(_.keys(entries[principalId2]).length, 350); + + // Verify that mrvisser is a viewer of all items, and that simong is a + // manager of all items + // eslint-disable-next-line no-unused-vars + _.each(entries[principalId1], (role, resourceId) => { + assert.strictEqual(role, 'viewer'); + }); + + // eslint-disable-next-line no-unused-vars + _.each(entries[principalId2], (role, resourceId) => { + assert.strictEqual(role, 'manager'); + }); + + // Make sure they work individually + AuthzAPI.getRolesForPrincipalsAndResourceType([principalId1], ResourceTypes.CONTENT, (err, entries) => { + assert.ok(!err); + assert.strictEqual(_.keys(entries).length, 1); + assert.strictEqual(_.keys(entries[principalId1]).length, 300); + AuthzAPI.getRolesForPrincipalsAndResourceType( - [principalId1, principalId2], + [principalId2], ResourceTypes.CONTENT, (err, entries) => { assert.ok(!err); - - // Simong is a member of 350, mrvisser is a member of 300, but 50 of those overlap, so should be 600 unique entries - assert.strictEqual(_.keys(entries).length, 2); - assert.strictEqual(_.keys(entries[principalId1]).length, 300); + assert.strictEqual(_.keys(entries).length, 1); assert.strictEqual(_.keys(entries[principalId2]).length, 350); - // Verify that mrvisser is a viewer of all items, and that simong is a - // manager of all items - // eslint-disable-next-line no-unused-vars - _.each(entries[principalId1], (role, resourceId) => { - assert.strictEqual(role, 'viewer'); - }); - - // eslint-disable-next-line no-unused-vars - _.each(entries[principalId2], (role, resourceId) => { - assert.strictEqual(role, 'manager'); - }); - - // Make sure they work individually - AuthzAPI.getRolesForPrincipalsAndResourceType( - [principalId1], - ResourceTypes.CONTENT, - (err, entries) => { - assert.ok(!err); - assert.strictEqual(_.keys(entries).length, 1); - assert.strictEqual(_.keys(entries[principalId1]).length, 300); - - AuthzAPI.getRolesForPrincipalsAndResourceType( - [principalId2], - ResourceTypes.CONTENT, - (err, entries) => { - assert.ok(!err); - assert.strictEqual(_.keys(entries).length, 1); - assert.strictEqual(_.keys(entries[principalId2]).length, 350); - - return callback(); - } - ); - } - ); + return callback(); } ); - } - ); - } - ); - } - ); + }); + } + ); + }); + }); + }); }); it('verify validation', callback => { - const principalId1 = AuthzUtil.toId( - PrincipalTypes.USER, - 'testGetRolesForPrincipalsAndResourceType', - 'mrvisser' - ); + const principalId1 = AuthzUtil.toId(PrincipalTypes.USER, 'testGetRolesForPrincipalsAndResourceType', 'mrvisser'); // Try it with no provided principals - AuthzAPI.getRolesForPrincipalsAndResourceType( - undefined, - ResourceTypes.CONTENT, - (err, entries) => { + AuthzAPI.getRolesForPrincipalsAndResourceType(undefined, ResourceTypes.CONTENT, (err, entries) => { + assert.ok(err); + assert.ok(!entries); + // Try it with no resource type + AuthzAPI.getRolesForPrincipalsAndResourceType([principalId1], undefined, (err, entries) => { assert.ok(err); assert.ok(!entries); - // Try it with no resource type - AuthzAPI.getRolesForPrincipalsAndResourceType( - [principalId1], - undefined, - (err, entries) => { - assert.ok(err); - assert.ok(!entries); - callback(); - } - ); - } - ); + callback(); + }); + }); }); }); }); diff --git a/packages/oae-authz/tests/test-validator.js b/packages/oae-authz/tests/test-validator.js index f113379b1d..33484e0216 100644 --- a/packages/oae-authz/tests/test-validator.js +++ b/packages/oae-authz/tests/test-validator.js @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const { Validator } = require('oae-authz/lib/validator'); +import assert from 'assert'; +import { Validator } from 'oae-authz/lib/validator'; describe('Authz-Validator', () => { describe('#isResourceId()', () => { diff --git a/packages/oae-config/lib/api.js b/packages/oae-config/lib/api.js index 7483541c73..16ac565a53 100644 --- a/packages/oae-config/lib/api.js +++ b/packages/oae-config/lib/api.js @@ -13,16 +13,18 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); -const clone = require('clone'); +import util from 'util'; +import _ from 'underscore'; +import clone from 'clone'; -const EmitterAPI = require('oae-emitter'); -const IO = require('oae-util/lib/io'); -const OaeUtil = require('oae-util/lib/util'); -const Pubsub = require('oae-util/lib/pubsub'); -const { Validator } = require('oae-util/lib/validator'); -const log = require('oae-logger').logger('oae-config'); +import * as EmitterAPI from 'oae-emitter'; +import IO from 'oae-util/lib/io'; +import OaeUtil from 'oae-util/lib/util'; +import Pubsub from 'oae-util/lib/pubsub'; +import { Validator } from 'oae-util/lib/validator'; +import { logger } from 'oae-logger'; + +const log = logger('oae-config'); // Will be used to cache the global OAE config let config = null; @@ -62,10 +64,7 @@ Pubsub.emitter.on('oae-config', tenantAlias => { // Update the tenant configuration that was updated updateTenantConfig(tenantAlias, err => { if (err) { - return log().error( - { err, tenantAlias }, - 'Error refreshing cached configuration after update' - ); + return log().error({ err, tenantAlias }, 'Error refreshing cached configuration after update'); } eventEmitter.emit('update', tenantAlias); @@ -83,7 +82,7 @@ Pubsub.emitter.on('oae-config', tenantAlias => { * @return {Function} getValue Function that returns the cached a cached config value from the provided module * @throws {Error} Error thrown when no module id has been provided */ -config = function(moduleId) { +const setUpConfig = function(moduleId) { // Parameter validation if (!moduleId) { throw new Error('A module id must be provided'); @@ -100,15 +99,11 @@ config = function(moduleId) { * @return {Boolean|String|Number|Object} cachedConfiguration The requested config value e.g. `true`. This will be null if the config element cannot be found */ getValue(tenantAlias, featureKey, elementKey) { - const configValueInfo = _resolveConfigValueInfo( - tenantAlias, - moduleId, - featureKey, - elementKey - ); + const configValueInfo = _resolveConfigValueInfo(tenantAlias, moduleId, featureKey, elementKey); if (configValueInfo) { return configValueInfo.value; } + return null; }, @@ -141,6 +136,7 @@ const getSchema = function(ctx, callback) { if (!ctx.user() || !ctx.user().isAdmin(ctx.tenant().alias)) { return callback({ code: 401, msg: 'Only global and tenant admin can get the config schema' }); } + if (ctx.user().isGlobalAdmin()) { return callback(null, cachedGlobalSchema); } @@ -177,18 +173,14 @@ const getTenantConfig = function(ctx, tenantAlias, callback) { if (!isGlobalAdmin && !isTenantAdmin && element.suppress) { return; } + if (isTenantAdmin && element.globalAdminOnly) { return; } // The current user can see this value, so get the value info (value and timestamp) // to populate it in the tenant configuration response - const configValueInfo = _resolveConfigValueInfo( - tenantAlias, - moduleKey, - featureKey, - elementKey - ); + const configValueInfo = _resolveConfigValueInfo(tenantAlias, moduleKey, featureKey, elementKey); // Finally set the value on the tenant configuration tenantConfig[moduleKey] = tenantConfig[moduleKey] || {}; @@ -323,11 +315,11 @@ const _cacheSchema = function(callback) { let complete = false; /*! - * Get the configuration files for a given module and create the schema for global and tenant administrators - * when all configuration files have been loaded - * - * @param {String} module The module we're getting the configuration for. e.g., `oae-principals` - */ + * Get the configuration files for a given module and create the schema for global and tenant administrators + * when all configuration files have been loaded + * + * @param {String} module The module we're getting the configuration for. e.g., `oae-principals` + */ const getModuleSchema = function(module) { const dir = OaeUtil.getNodeModulesDir() + module + '/config/'; // Get a list of the available config files @@ -335,6 +327,7 @@ const _cacheSchema = function(callback) { if (complete) { return; } + if (err) { complete = true; return callback(err); @@ -490,10 +483,7 @@ const _rowsToConfig = function(rows) { timestamp }; } catch (error) { - log().error( - { err: error, key, value }, - 'Failed to parse configuration value from database' - ); + log().error({ err: error, key, value }, 'Failed to parse configuration value from database'); } } } @@ -599,8 +589,7 @@ const updateConfig = function(ctx, tenantAlias, configValues, callback) { validator .check(configFieldNames.length, { code: 400, - msg: - 'Missing configuration. Example configuration: {"oae-authentication/twitter/enabled": false}' + msg: 'Missing configuration. Example configuration: {"oae-authentication/twitter/enabled": false}' }) .min(1); @@ -623,6 +612,7 @@ const updateConfig = function(ctx, tenantAlias, configValues, callback) { msg: util.format('Config key "%s" does not exist', configFieldName) }); } + if (!_canUpdateConfigValue(ctx, tenantAlias, parts[0], parts[1], parts[2])) { return callback({ code: 401, @@ -630,6 +620,7 @@ const updateConfig = function(ctx, tenantAlias, configValues, callback) { }); } } + if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -651,13 +642,11 @@ const updateConfig = function(ctx, tenantAlias, configValues, callback) { const storageKey = _generateColumnKey(module, feature, element); if (optionalKey) { - const currentConfigValue = _resolveConfigValueInfo(tenantAlias, module, feature, element) - .value; + const currentConfigValue = _resolveConfigValueInfo(tenantAlias, module, feature, element).value; // If we specified an optional key, we're only partially updating an element's value // We need to merge it with the existing value - aggregatedValues[storageKey] = - aggregatedValues[storageKey] || _.extend({}, currentConfigValue) || {}; + aggregatedValues[storageKey] = aggregatedValues[storageKey] || _.extend({}, currentConfigValue) || {}; aggregatedValues[storageKey][optionalKey] = value; } else { aggregatedValues[storageKey] = value; @@ -736,6 +725,7 @@ const clearConfig = function(ctx, tenantAlias, configFields, callback) { msg: util.format('Config value "%s" does not exist', configFields[i]) }); } + if (!_canUpdateConfigValue(ctx, tenantAlias, configField[0], configField[1], configField[2])) { return callback({ code: 401, @@ -743,6 +733,7 @@ const clearConfig = function(ctx, tenantAlias, configFields, callback) { }); } } + if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -761,8 +752,7 @@ const clearConfig = function(ctx, tenantAlias, configFields, callback) { // If no optional key was specified, we can delete the entire column if (optionalKey) { - const currentConfigValue = _resolveConfigValueInfo(tenantAlias, module, feature, element) - .value; + const currentConfigValue = _resolveConfigValueInfo(tenantAlias, module, feature, element).value; // It's possible we've already deleted an optional key within this element const value = rowChanges[columnKey] || _.extend({}, currentConfigValue); @@ -788,6 +778,7 @@ const clearConfig = function(ctx, tenantAlias, configFields, callback) { parameters: [tenantAlias, columnName] }; } + if (_.isObject(value)) { value = JSON.stringify(value); } @@ -866,9 +857,9 @@ const _parseColumnKey = function(columnKey) { }; }; -module.exports = { +export { eventEmitter, - config, + setUpConfig, getSchema, getTenantConfig, initConfig, diff --git a/packages/oae-config/lib/fields.js b/packages/oae-config/lib/fields.js index 3864e4bfb7..c36225628b 100644 --- a/packages/oae-config/lib/fields.js +++ b/packages/oae-config/lib/fields.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const OaeUtil = require('oae-util/lib/util'); +import * as OaeUtil from 'oae-util/lib/util'; /** * A basic field object that contains all the field properties and @@ -71,9 +71,9 @@ const Bool = function(name, description, defaultValue, options) { const field = new BaseField('boolean', name, description, defaultValue, options); /*! - * @return {Boolean} Convert the given Cassandra column value to a boolean - * @see BaseField#deserialize - */ + * @return {Boolean} Convert the given Cassandra column value to a boolean + * @see BaseField#deserialize + */ field.deserialize = function(columnValue) { return OaeUtil.castToBoolean(columnValue); }; @@ -155,10 +155,4 @@ const List = function(name, description, defaultValue, list, options) { return field; }; -module.exports = { - Bool, - Text, - InternationalizableText, - Radio, - List -}; +export { Bool, Text, InternationalizableText, Radio, List }; diff --git a/packages/oae-config/lib/init.js b/packages/oae-config/lib/init.js index 2e18c36411..18e16cdebc 100644 --- a/packages/oae-config/lib/init.js +++ b/packages/oae-config/lib/init.js @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -const ConfigAPI = require('oae-config'); +import { initConfig } from 'oae-config'; -module.exports = function(config, callback) { - return ConfigAPI.initConfig(config, callback); +export const init = function(config, callback) { + return initConfig(config, callback); }; diff --git a/packages/oae-config/lib/migration.js b/packages/oae-config/lib/migration.js index 55234173dc..39cde42999 100644 --- a/packages/oae-config/lib/migration.js +++ b/packages/oae-config/lib/migration.js @@ -15,4 +15,4 @@ const ensureSchema = function(callback) { ); }; -module.exports = { ensureSchema }; +export { ensureSchema }; diff --git a/packages/oae-config/lib/rest.js b/packages/oae-config/lib/rest.js index 31da0c4a9c..0307a4f56d 100644 --- a/packages/oae-config/lib/rest.js +++ b/packages/oae-config/lib/rest.js @@ -13,10 +13,9 @@ * permissions and limitations under the License. */ -const OAE = require('oae-util/lib/oae'); -const OaeUtil = require('oae-util/lib/util'); - -const ConfigAPI = require('oae-config'); +import * as OAE from 'oae-util/lib/oae'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as ConfigAPI from 'oae-config'; /** * Convenience method to handle getting the configuration schema for the current tenant diff --git a/packages/oae-config/lib/test/util.js b/packages/oae-config/lib/test/util.js index d4df8d9c1e..336b0632d4 100644 --- a/packages/oae-config/lib/test/util.js +++ b/packages/oae-config/lib/test/util.js @@ -13,10 +13,9 @@ * permissions and limitations under the License. */ -const Counter = require('oae-util/lib/counter'); -const RestAPI = require('oae-rest'); - -const ConfigAPI = require('oae-config'); +import Counter from 'oae-util/lib/counter'; +import { Config } from 'oae-rest'; +import * as ConfigAPI from 'oae-config'; // Maintains the number of updates that have not yet been refreshed in the application cache const updateCounter = new Counter(); @@ -29,7 +28,7 @@ const updateCounter = new Counter(); * For method parameter descriptions, @see RestAPI.Config#updateConfig */ const updateConfigAndWait = function(restCtx, tenantAlias, configUpdate, callback) { - RestAPI.Config.updateConfig(restCtx, tenantAlias, configUpdate, err => { + Config.updateConfig(restCtx, tenantAlias, configUpdate, err => { if (err) { return callback(err); } @@ -47,7 +46,7 @@ const updateConfigAndWait = function(restCtx, tenantAlias, configUpdate, callbac * For method parameter descriptions, @see RestAPI.Config#clearConfig */ const clearConfigAndWait = function(restCtx, tenantAlias, configFields, callback) { - RestAPI.Config.clearConfig(restCtx, tenantAlias, configFields, err => { + Config.clearConfig(restCtx, tenantAlias, configFields, err => { if (err) { return callback(err); } @@ -89,8 +88,4 @@ ConfigAPI.eventEmitter.on('preCache', _incrementUpdateCount); ConfigAPI.eventEmitter.on('update', _decrementUpdateCount); ConfigAPI.eventEmitter.on('cached', _decrementUpdateCount); -module.exports = { - clearConfigAndWait, - updateConfigAndWait, - whenConfigUpdated -}; +export { clearConfigAndWait, updateConfigAndWait, whenConfigUpdated }; diff --git a/packages/oae-config/tests/test-config.js b/packages/oae-config/tests/test-config.js index 76f0e1d788..d226bb6bb5 100644 --- a/packages/oae-config/tests/test-config.js +++ b/packages/oae-config/tests/test-config.js @@ -13,15 +13,15 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const ConfigTestUtil = require('oae-config/lib/test/util'); -const RestAPI = require('oae-rest'); -const TenantsTestUtil = require('oae-tenants/lib/test/util'); -const TestsUtil = require('oae-tests'); +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; +import * as TestsUtil from 'oae-tests'; -const ConfigAPI = require('oae-config'); +import * as ConfigAPI from 'oae-config'; describe('Configuration', () => { // Rest context that can be used every time we need to make a request as an anonymous user on the Cambridge tenant @@ -36,8 +36,8 @@ describe('Configuration', () => { let johnRestContext = null; /*! - * Function that will fill up the global admin, tenant admin and anymous rest context - */ + * Function that will fill up the global admin, tenant admin and anymous rest context + */ before(callback => { // Fill up the anonymous cam rest context anonymousCamRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); @@ -56,8 +56,8 @@ describe('Configuration', () => { }); /*! - * Clear the configuration values that are changed during tests - */ + * Clear the configuration values that are changed during tests + */ afterEach(callback => { // An update object to apply that will clear the values for the global admin const globalClearValues = [ @@ -104,10 +104,7 @@ describe('Configuration', () => { assert.ok(!err); assert.ok(schema); assert.ok(schema['oae-authentication'].title); - assert.strictEqual( - schema['oae-authentication'].twitter.elements.enabled.defaultValue, - true - ); + assert.strictEqual(schema['oae-authentication'].twitter.elements.enabled.defaultValue, true); // Verify that the anonymous users can't retrieve the schema RestAPI.Config.getSchema(anonymousGlobalRestContext, (err, schema) => { @@ -127,10 +124,7 @@ describe('Configuration', () => { assert.ok(!err); assert.ok(schema); assert.ok(schema['oae-authentication'].title); - assert.strictEqual( - schema['oae-authentication'].twitter.elements.enabled.defaultValue, - true - ); + assert.strictEqual(schema['oae-authentication'].twitter.elements.enabled.defaultValue, true); // Verify that regular tenant users can't retrieve the schema RestAPI.Config.getSchema(johnRestContext, (err, schema) => { @@ -158,19 +152,13 @@ describe('Configuration', () => { assert.ok(!err); assert.ok(schema); assert.ok(schema['oae-content'].title); - assert.strictEqual( - schema['oae-content'].storage.elements['amazons3-access-key'].defaultValue, - '' - ); + assert.strictEqual(schema['oae-content'].storage.elements['amazons3-access-key'].defaultValue, ''); RestAPI.Config.getSchema(camAdminRestContext, (err, schema) => { assert.ok(!err); assert.ok(schema); assert.ok(schema['oae-content'].title); - assert.strictEqual( - schema['oae-content'].storage.elements['amazons3-access-key'], - undefined - ); + assert.strictEqual(schema['oae-content'].storage.elements['amazons3-access-key'], undefined); callback(); }); }); @@ -194,18 +182,12 @@ describe('Configuration', () => { * Test that verifies that a single configuration value can be retrieved from the cached configuration */ it('verify get single config value', callback => { - const AuthenticationConfig = ConfigAPI.config('oae-authentication'); - const PrincipalsConfig = ConfigAPI.config('oae-principals'); + const AuthenticationConfig = ConfigAPI.setUpConfig('oae-authentication'); + const PrincipalsConfig = ConfigAPI.setUpConfig('oae-principals'); // Retrieve a non-existing value - assert.strictEqual( - AuthenticationConfig.getValue(global.oaeTests.tenants.cam.alias, 'sso', 'enabled'), - null - ); + assert.strictEqual(AuthenticationConfig.getValue(global.oaeTests.tenants.cam.alias, 'sso', 'enabled'), null); // Retrieve a boolean value - assert.strictEqual( - AuthenticationConfig.getValue(global.oaeTests.tenants.cam.alias, 'twitter', 'enabled'), - true - ); + assert.strictEqual(AuthenticationConfig.getValue(global.oaeTests.tenants.cam.alias, 'twitter', 'enabled'), true); // Retrieve a string value assert.strictEqual( PrincipalsConfig.getValue(global.oaeTests.tenants.cam.alias, 'user', 'defaultLanguage'), @@ -225,16 +207,13 @@ describe('Configuration', () => { it('verify validation', callback => { // Verify that initializing a config factory needs a module name. This should throw an error assert.throws(() => { - ConfigAPI.config(); + ConfigAPI.setUpConfig(); }); // Verify that a feature needs to be provided when getting a config value - const AuthenticationConfig = ConfigAPI.config('oae-authentication'); + const AuthenticationConfig = ConfigAPI.setUpConfig('oae-authentication'); assert.strictEqual(AuthenticationConfig.getValue(global.oaeTests.tenants.cam.alias), null); // Verify that an element needs to be provided when getting a config value - assert.strictEqual( - AuthenticationConfig.getValue(global.oaeTests.tenants.cam.alias, 'twitter'), - null - ); + assert.strictEqual(AuthenticationConfig.getValue(global.oaeTests.tenants.cam.alias, 'twitter'), null); callback(); }); @@ -242,26 +221,16 @@ describe('Configuration', () => { * Test that verifies the last updated timestamp is reflected when a value gets updated */ it('verify the last updated timestamp increases when updated', callback => { - const PrincipalsConfig = ConfigAPI.config('oae-principals'); + const PrincipalsConfig = ConfigAPI.setUpConfig('oae-principals'); // Not passing in any of the `tenantAlias`, `feature` or `element` parameters should result in the epoch date being returned assert.strictEqual(PrincipalsConfig.getLastUpdated().getTime(), 0); - assert.strictEqual( - PrincipalsConfig.getLastUpdated(global.oaeTests.tenants.cam.alias).getTime(), - 0 - ); - assert.strictEqual( - PrincipalsConfig.getLastUpdated(global.oaeTests.tenants.cam.alias, 'recaptcha').getTime(), - 0 - ); + assert.strictEqual(PrincipalsConfig.getLastUpdated(global.oaeTests.tenants.cam.alias).getTime(), 0); + assert.strictEqual(PrincipalsConfig.getLastUpdated(global.oaeTests.tenants.cam.alias, 'recaptcha').getTime(), 0); // Passing in an unknown element should result in the epoch date assert.strictEqual( - PrincipalsConfig.getLastUpdated( - global.oaeTests.tenants.cam.alias, - 'careful', - 'now' - ).getTime(), + PrincipalsConfig.getLastUpdated(global.oaeTests.tenants.cam.alias, 'careful', 'now').getTime(), 0 ); @@ -272,11 +241,7 @@ describe('Configuration', () => { assert.ok(!err); // Record the current value of the recaptcha config update timestamp - const recaptchaUpdateTime = PrincipalsConfig.getLastUpdated( - tenantAlias, - 'recaptcha', - 'enabled' - ).getTime(); + const recaptchaUpdateTime = PrincipalsConfig.getLastUpdated(tenantAlias, 'recaptcha', 'enabled').getTime(); assert.ok(recaptchaUpdateTime > 0); // Enable recaptcha on the tenant @@ -289,8 +254,7 @@ describe('Configuration', () => { // Ensure the config value update time is larger than it was before assert.ok( - PrincipalsConfig.getLastUpdated(tenantAlias, 'recaptcha', 'enabled').getTime() > - recaptchaUpdateTime + PrincipalsConfig.getLastUpdated(tenantAlias, 'recaptcha', 'enabled').getTime() > recaptchaUpdateTime ); return callback(); } @@ -302,18 +266,14 @@ describe('Configuration', () => { * Test that verifies the last updated timestamp does not get updated when the value does not change */ it('verify the last updated timestamp does not change when the config value did not change', callback => { - const PrincipalsConfig = ConfigAPI.config('oae-principals'); + const PrincipalsConfig = ConfigAPI.setUpConfig('oae-principals'); const tenantAlias = TenantsTestUtil.generateTestTenantAlias(); const host = TenantsTestUtil.generateTestTenantHost(); TestsUtil.createTenantWithAdmin(tenantAlias, host, (err, tenant, tenantAdminRestContext) => { assert.ok(!err); // Record the current value of the recaptcha config update timestamp - const recaptchaUpdateTime = PrincipalsConfig.getLastUpdated( - tenantAlias, - 'recaptcha', - 'enabled' - ).getTime(); + const recaptchaUpdateTime = PrincipalsConfig.getLastUpdated(tenantAlias, 'recaptcha', 'enabled').getTime(); assert.ok(recaptchaUpdateTime > 0); // Update a different configuration field. Let's enable the twitter! @@ -348,10 +308,7 @@ describe('Configuration', () => { // Verify that a public value is present assert.strictEqual(config['oae-authentication'].twitter.enabled, true); // Verify that a suppressed value is present - assert.strictEqual( - config['oae-principals'].recaptcha.privateKey, - '6LcFWdYSAAAAANrHjt2Y5VJXoICHa95PFDarVcGs' - ); + assert.strictEqual(config['oae-principals'].recaptcha.privateKey, '6LcFWdYSAAAAANrHjt2Y5VJXoICHa95PFDarVcGs'); // Verify that a globalAdminOnly value is present assert.strictEqual(config['oae-content'].storage['amazons3-access-key'], ''); @@ -381,10 +338,7 @@ describe('Configuration', () => { // Verify that a public value is present assert.strictEqual(config['oae-authentication'].twitter.enabled, true); // Verify that a suppressed value is present - assert.strictEqual( - config['oae-principals'].recaptcha.privateKey, - '6LcFWdYSAAAAANrHjt2Y5VJXoICHa95PFDarVcGs' - ); + assert.strictEqual(config['oae-principals'].recaptcha.privateKey, '6LcFWdYSAAAAANrHjt2Y5VJXoICHa95PFDarVcGs'); // Verify that a globalAdminOnly values are not present assert.ok(!config['oae-content'].storage); @@ -528,19 +482,12 @@ describe('Configuration', () => { assert.ok(!err); // Verify that the value reverts to the default - RestAPI.Config.getTenantConfig( - camAdminRestContext, - null, - (err, config) => { - assert.ok(!err); - assert.ok(config); - assert.strictEqual( - config['oae-authentication'].twitter.enabled, - true - ); - callback(); - } - ); + RestAPI.Config.getTenantConfig(camAdminRestContext, null, (err, config) => { + assert.ok(!err); + assert.ok(config); + assert.strictEqual(config['oae-authentication'].twitter.enabled, true); + callback(); + }); } ); }); @@ -573,10 +520,7 @@ describe('Configuration', () => { ConfigTestUtil.clearConfigAndWait( camAdminRestContext, null, - [ - 'oae-principals/termsAndConditions/text/en_GB', - 'oae-principals/termsAndConditions/text' - ], + ['oae-principals/termsAndConditions/text/en_GB', 'oae-principals/termsAndConditions/text'], err => { assert.strictEqual(err.code, 400); @@ -584,10 +528,7 @@ describe('Configuration', () => { ConfigTestUtil.clearConfigAndWait( camAdminRestContext, null, - [ - 'oae-principals/termsAndConditions/text', - 'oae-principals/termsAndConditions/text/en_GB' - ], + ['oae-principals/termsAndConditions/text', 'oae-principals/termsAndConditions/text/en_GB'], err => { assert.strictEqual(err.code, 400); @@ -595,31 +536,13 @@ describe('Configuration', () => { RestAPI.Config.getTenantConfig(camAdminRestContext, null, (err, config) => { assert.ok(!err); assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); - assert.strictEqual( - _.keys(config['oae-principals'].termsAndConditions.text).length, - 6 - ); + assert.strictEqual(_.keys(config['oae-principals'].termsAndConditions.text).length, 6); assert.strictEqual(config['oae-principals'].termsAndConditions.text.default, ''); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_CA, - 'Canadian English' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_GB, - 'British English' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_US, - 'American English' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.fr_BE, - 'Belgian French' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.fr_FR, - 'French French' - ); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.en_CA, 'Canadian English'); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.en_GB, 'British English'); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.en_US, 'American English'); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.fr_BE, 'Belgian French'); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.fr_FR, 'French French'); return callback(); }); @@ -650,26 +573,11 @@ describe('Configuration', () => { assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); assert.strictEqual(_.keys(config['oae-principals'].termsAndConditions.text).length, 6); assert.strictEqual(config['oae-principals'].termsAndConditions.text.default, ''); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_CA, - 'Canadian English' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_GB, - 'British English' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_US, - 'American English' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.fr_BE, - 'Belgian French' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.fr_FR, - 'French French' - ); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.en_CA, 'Canadian English'); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.en_GB, 'British English'); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.en_US, 'American English'); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.fr_BE, 'Belgian French'); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.fr_FR, 'French French'); // Clearing just the British English value should not affect the other values ConfigTestUtil.clearConfigAndWait( @@ -683,37 +591,19 @@ describe('Configuration', () => { RestAPI.Config.getTenantConfig(camAdminRestContext, null, (err, config) => { assert.ok(!err); assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); - assert.strictEqual( - _.keys(config['oae-principals'].termsAndConditions.text).length, - 5 - ); + assert.strictEqual(_.keys(config['oae-principals'].termsAndConditions.text).length, 5); assert.ok(!config['oae-principals'].termsAndConditions.text.en_GB); assert.strictEqual(config['oae-principals'].termsAndConditions.text.default, ''); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_CA, - 'Canadian English' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_US, - 'American English' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.fr_BE, - 'Belgian French' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.fr_FR, - 'French French' - ); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.en_CA, 'Canadian English'); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.en_US, 'American English'); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.fr_BE, 'Belgian French'); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.fr_FR, 'French French'); // Try clearing multiple keys ConfigTestUtil.clearConfigAndWait( camAdminRestContext, null, - [ - 'oae-principals/termsAndConditions/text/fr_BE', - 'oae-principals/termsAndConditions/text/fr_FR' - ], + ['oae-principals/termsAndConditions/text/fr_BE', 'oae-principals/termsAndConditions/text/fr_FR'], err => { assert.ok(!err); @@ -721,25 +611,13 @@ describe('Configuration', () => { RestAPI.Config.getTenantConfig(camAdminRestContext, null, (err, config) => { assert.ok(!err); assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); - assert.strictEqual( - _.keys(config['oae-principals'].termsAndConditions.text).length, - 3 - ); + assert.strictEqual(_.keys(config['oae-principals'].termsAndConditions.text).length, 3); assert.ok(!config['oae-principals'].termsAndConditions.text.en_GB); assert.ok(!config['oae-principals'].termsAndConditions.text.fr_FR); assert.ok(!config['oae-principals'].termsAndConditions.text.fr_BE); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.default, - '' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_CA, - 'Canadian English' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_US, - 'American English' - ); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.default, ''); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.en_CA, 'Canadian English'); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.en_US, 'American English'); // Reset the T&C field in its entirety ConfigTestUtil.clearConfigAndWait( @@ -750,84 +628,54 @@ describe('Configuration', () => { assert.ok(!err); // Only the default key should be present - RestAPI.Config.getTenantConfig( - camAdminRestContext, - null, - (err, config) => { + RestAPI.Config.getTenantConfig(camAdminRestContext, null, (err, config) => { + assert.ok(!err); + assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); + assert.strictEqual(_.keys(config['oae-principals'].termsAndConditions.text).length, 1); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.default, ''); + + // Check that we can still set a value + const configUpdate = { + 'oae-principals/termsAndConditions/text/en_CA': 'Canadian English', + 'oae-principals/termsAndConditions/text/en_GB': 'British English', + 'oae-principals/termsAndConditions/text/en_US': 'American English', + 'oae-principals/termsAndConditions/text/fr_BE': 'Belgian French', + 'oae-principals/termsAndConditions/text/fr_FR': 'French French' + }; + ConfigTestUtil.updateConfigAndWait(camAdminRestContext, null, configUpdate, err => { assert.ok(!err); - assert.ok( - _.isObject(config['oae-principals'].termsAndConditions.text) - ); - assert.strictEqual( - _.keys(config['oae-principals'].termsAndConditions.text).length, - 1 - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.default, - '' - ); - - // Check that we can still set a value - const configUpdate = { - 'oae-principals/termsAndConditions/text/en_CA': 'Canadian English', - 'oae-principals/termsAndConditions/text/en_GB': 'British English', - 'oae-principals/termsAndConditions/text/en_US': 'American English', - 'oae-principals/termsAndConditions/text/fr_BE': 'Belgian French', - 'oae-principals/termsAndConditions/text/fr_FR': 'French French' - }; - ConfigTestUtil.updateConfigAndWait( - camAdminRestContext, - null, - configUpdate, - err => { - assert.ok(!err); - - // Verify the update - RestAPI.Config.getTenantConfig( - camAdminRestContext, - null, - (err, config) => { - assert.ok(!err); - assert.ok( - _.isObject(config['oae-principals'].termsAndConditions.text) - ); - assert.strictEqual( - _.keys(config['oae-principals'].termsAndConditions.text) - .length, - 6 - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.default, - '' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_CA, - 'Canadian English' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_GB, - 'British English' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_US, - 'American English' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.fr_BE, - 'Belgian French' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.fr_FR, - 'French French' - ); - return callback(); - } - ); - } - ); - } - ); + // Verify the update + RestAPI.Config.getTenantConfig(camAdminRestContext, null, (err, config) => { + assert.ok(!err); + assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); + assert.strictEqual(_.keys(config['oae-principals'].termsAndConditions.text).length, 6); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.default, ''); + assert.strictEqual( + config['oae-principals'].termsAndConditions.text.en_CA, + 'Canadian English' + ); + assert.strictEqual( + config['oae-principals'].termsAndConditions.text.en_GB, + 'British English' + ); + assert.strictEqual( + config['oae-principals'].termsAndConditions.text.en_US, + 'American English' + ); + assert.strictEqual( + config['oae-principals'].termsAndConditions.text.fr_BE, + 'Belgian French' + ); + assert.strictEqual( + config['oae-principals'].termsAndConditions.text.fr_FR, + 'French French' + ); + + return callback(); + }); + }); + }); } ); }); @@ -845,40 +693,29 @@ describe('Configuration', () => { */ it('verify get tenant config through global tenant', callback => { // Get the config as an admin user - RestAPI.Config.getTenantConfig( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - (err, config) => { + RestAPI.Config.getTenantConfig(globalAdminRestContext, global.oaeTests.tenants.cam.alias, (err, config) => { + assert.ok(!err); + assert.ok(config); + // Verify that a public value is present + assert.strictEqual(config['oae-authentication'].twitter.enabled, true); + // Verify that a suppressed value is present + assert.strictEqual(config['oae-principals'].recaptcha.privateKey, '6LcFWdYSAAAAANrHjt2Y5VJXoICHa95PFDarVcGs'); + // Verify that a globalAdminOnly value is present + assert.strictEqual(config['oae-content'].storage['amazons3-access-key'], ''); + + // Get the config as an anonymous user + RestAPI.Config.getTenantConfig(anonymousGlobalRestContext, global.oaeTests.tenants.cam.alias, (err, config) => { assert.ok(!err); assert.ok(config); // Verify that a public value is present assert.strictEqual(config['oae-authentication'].twitter.enabled, true); - // Verify that a suppressed value is present - assert.strictEqual( - config['oae-principals'].recaptcha.privateKey, - '6LcFWdYSAAAAANrHjt2Y5VJXoICHa95PFDarVcGs' - ); - // Verify that a globalAdminOnly value is present - assert.strictEqual(config['oae-content'].storage['amazons3-access-key'], ''); - - // Get the config as an anonymous user - RestAPI.Config.getTenantConfig( - anonymousGlobalRestContext, - global.oaeTests.tenants.cam.alias, - (err, config) => { - assert.ok(!err); - assert.ok(config); - // Verify that a public value is present - assert.strictEqual(config['oae-authentication'].twitter.enabled, true); - // Verify that a suppressed value is not present - assert.strictEqual(config['oae-principals'].recaptcha.privateKey, undefined); - // Verify that a globalAdminOnly value is not present - assert.ok(!config['oae-content'].storage); - callback(); - } - ); - } - ); + // Verify that a suppressed value is not present + assert.strictEqual(config['oae-principals'].recaptcha.privateKey, undefined); + // Verify that a globalAdminOnly value is not present + assert.ok(!config['oae-content'].storage); + callback(); + }); + }); }); /** @@ -928,10 +765,7 @@ describe('Configuration', () => { RestAPI.Config.getTenantConfig(johnRestContext, null, (err, config) => { assert.ok(!err); assert.ok(config); - assert.strictEqual( - config['oae-principals'].recaptcha.privateKey, - undefined - ); + assert.strictEqual(config['oae-principals'].recaptcha.privateKey, undefined); callback(); }); }); @@ -962,62 +796,48 @@ describe('Configuration', () => { assert.strictEqual(config['oae-authentication'].twitter.enabled, false); // Validate that the new value can be retrieved through the global admin - RestAPI.Config.getTenantConfig( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - (err, config) => { - assert.ok(!err); - assert.ok(config); - assert.strictEqual(config['oae-authentication'].twitter.enabled, false); + RestAPI.Config.getTenantConfig(globalAdminRestContext, global.oaeTests.tenants.cam.alias, (err, config) => { + assert.ok(!err); + assert.ok(config); + assert.strictEqual(config['oae-authentication'].twitter.enabled, false); - // Set a new value for a suppressed config value - ConfigTestUtil.updateConfigAndWait( - camAdminRestContext, - null, - { 'oae-principals/recaptcha/privateKey': 'newTenantKey' }, - err => { + // Set a new value for a suppressed config value + ConfigTestUtil.updateConfigAndWait( + camAdminRestContext, + null, + { 'oae-principals/recaptcha/privateKey': 'newTenantKey' }, + err => { + assert.ok(!err); + + // Validate that the tenant admin can see this as well + RestAPI.Config.getTenantConfig(camAdminRestContext, null, (err, config) => { assert.ok(!err); + assert.ok(config); + assert.strictEqual(config['oae-principals'].recaptcha.privateKey, 'newTenantKey'); - // Validate that the tenant admin can see this as well - RestAPI.Config.getTenantConfig(camAdminRestContext, null, (err, config) => { + // Validate that a non-admin user can still not see this + RestAPI.Config.getTenantConfig(johnRestContext, null, (err, config) => { assert.ok(!err); assert.ok(config); - assert.strictEqual( - config['oae-principals'].recaptcha.privateKey, - 'newTenantKey' - ); + assert.strictEqual(config['oae-principals'].recaptcha.privateKey, undefined); - // Validate that a non-admin user can still not see this - RestAPI.Config.getTenantConfig(johnRestContext, null, (err, config) => { + // Validate that the global admin still has the old values + RestAPI.Config.getTenantConfig(globalAdminRestContext, null, (err, config) => { assert.ok(!err); assert.ok(config); + assert.strictEqual(config['oae-authentication'].twitter.enabled, true); assert.strictEqual( config['oae-principals'].recaptcha.privateKey, - undefined + '6LcFWdYSAAAAANrHjt2Y5VJXoICHa95PFDarVcGs' ); - // Validate that the global admin still has the old values - RestAPI.Config.getTenantConfig( - globalAdminRestContext, - null, - (err, config) => { - assert.ok(!err); - assert.ok(config); - assert.strictEqual(config['oae-authentication'].twitter.enabled, true); - assert.strictEqual( - config['oae-principals'].recaptcha.privateKey, - '6LcFWdYSAAAAANrHjt2Y5VJXoICHa95PFDarVcGs' - ); - - return callback(); - } - ); + return callback(); }); }); - } - ); - } - ); + }); + } + ); + }); }); } ); @@ -1035,23 +855,19 @@ describe('Configuration', () => { assert.ok(!err); // Validate that the change has been made from the global admin - RestAPI.Config.getTenantConfig( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - (err, config) => { + RestAPI.Config.getTenantConfig(globalAdminRestContext, global.oaeTests.tenants.cam.alias, (err, config) => { + assert.ok(!err); + assert.ok(config); + assert.strictEqual(config['oae-authentication'].twitter.enabled, false); + + // Validate that the change has been made from the tenant admin + RestAPI.Config.getTenantConfig(camAdminRestContext, null, (err, config) => { assert.ok(!err); assert.ok(config); assert.strictEqual(config['oae-authentication'].twitter.enabled, false); - - // Validate that the change has been made from the tenant admin - RestAPI.Config.getTenantConfig(camAdminRestContext, null, (err, config) => { - assert.ok(!err); - assert.ok(config); - assert.strictEqual(config['oae-authentication'].twitter.enabled, false); - callback(); - }); - } - ); + callback(); + }); + }); } ); }); @@ -1061,128 +877,107 @@ describe('Configuration', () => { */ it('verify config value coercion', callback => { // Get Boolean config value that's supposed to be `true` - RestAPI.Config.getTenantConfig( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - (err, config) => { - assert.ok(!err); - assert.ok(config); - assert.strictEqual(config['oae-authentication'].local.enabled, true); + RestAPI.Config.getTenantConfig(globalAdminRestContext, global.oaeTests.tenants.cam.alias, (err, config) => { + assert.ok(!err); + assert.ok(config); + assert.strictEqual(config['oae-authentication'].local.enabled, true); - // Change the value to false using '0', which would be used by checkboxes - ConfigTestUtil.updateConfigAndWait( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - { 'oae-authentication/local/enabled': '0' }, - err => { + // Change the value to false using '0', which would be used by checkboxes + ConfigTestUtil.updateConfigAndWait( + globalAdminRestContext, + global.oaeTests.tenants.cam.alias, + { 'oae-authentication/local/enabled': '0' }, + err => { + assert.ok(!err); + RestAPI.Config.getTenantConfig(globalAdminRestContext, global.oaeTests.tenants.cam.alias, (err, config) => { assert.ok(!err); - RestAPI.Config.getTenantConfig( + assert.ok(config); + assert.strictEqual(config['oae-authentication'].local.enabled, false); + + // Change the value to true using '1', which would be used by checkboxes + ConfigTestUtil.updateConfigAndWait( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - (err, config) => { + { 'oae-authentication/local/enabled': '1' }, + err => { assert.ok(!err); - assert.ok(config); - assert.strictEqual(config['oae-authentication'].local.enabled, false); - - // Change the value to true using '1', which would be used by checkboxes - ConfigTestUtil.updateConfigAndWait( + RestAPI.Config.getTenantConfig( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - { 'oae-authentication/local/enabled': '1' }, - err => { + (err, config) => { assert.ok(!err); - RestAPI.Config.getTenantConfig( + assert.ok(config); + assert.strictEqual(config['oae-authentication'].local.enabled, true); + + // Change the value to false using the 'false' string + ConfigTestUtil.updateConfigAndWait( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - (err, config) => { + { 'oae-authentication/local/enabled': 'false' }, + err => { assert.ok(!err); - assert.ok(config); - assert.strictEqual(config['oae-authentication'].local.enabled, true); - - // Change the value to false using the 'false' string - ConfigTestUtil.updateConfigAndWait( + RestAPI.Config.getTenantConfig( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - { 'oae-authentication/local/enabled': 'false' }, - err => { + (err, config) => { assert.ok(!err); - RestAPI.Config.getTenantConfig( + assert.ok(config); + assert.strictEqual(config['oae-authentication'].local.enabled, false); + + // Change the value back to true using the 'true' string + ConfigTestUtil.updateConfigAndWait( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - (err, config) => { + { 'oae-authentication/local/enabled': 'true' }, + err => { assert.ok(!err); - assert.ok(config); - assert.strictEqual( - config['oae-authentication'].local.enabled, - false - ); - - // Change the value back to true using the 'true' string - ConfigTestUtil.updateConfigAndWait( + RestAPI.Config.getTenantConfig( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - { 'oae-authentication/local/enabled': 'true' }, - err => { + (err, config) => { assert.ok(!err); + assert.ok(config); + assert.strictEqual(config['oae-authentication'].local.enabled, true); + + // Get non-Boolean config value RestAPI.Config.getTenantConfig( globalAdminRestContext, global.oaeTests.tenants.cam.alias, (err, config) => { assert.ok(!err); assert.ok(config); - assert.strictEqual( - config['oae-authentication'].local.enabled, - true - ); - // Get non-Boolean config value - RestAPI.Config.getTenantConfig( + // Change the value to '1' and ensure it isn't coerced to a boolean + ConfigTestUtil.updateConfigAndWait( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - (err, config) => { + { 'oae-email/general/fromName': '1' }, + err => { assert.ok(!err); - assert.ok(config); - - // Change the value to '1' and ensure it isn't coerced to a boolean - ConfigTestUtil.updateConfigAndWait( + RestAPI.Config.getTenantConfig( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - { 'oae-email/general/fromName': '1' }, - err => { + (err, config) => { assert.ok(!err); - RestAPI.Config.getTenantConfig( + assert.ok(config); + assert.strictEqual(config['oae-email'].general.fromName, '1'); + + // Change the value to '0' and ensure it isn't coerced to a boolean + ConfigTestUtil.updateConfigAndWait( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - (err, config) => { + { 'oae-email/general/fromName': '0' }, + err => { assert.ok(!err); - assert.ok(config); - assert.strictEqual( - config['oae-email'].general.fromName, - '1' - ); - - // Change the value to '0' and ensure it isn't coerced to a boolean - ConfigTestUtil.updateConfigAndWait( + RestAPI.Config.getTenantConfig( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - { 'oae-email/general/fromName': '0' }, - err => { + (err, config) => { assert.ok(!err); - RestAPI.Config.getTenantConfig( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - (err, config) => { - assert.ok(!err); - assert.ok(config); - assert.strictEqual( - config['oae-email'].general - .fromName, - '0' - ); + assert.ok(config); + assert.strictEqual(config['oae-email'].general.fromName, '0'); - return callback(); - } - ); + return callback(); } ); } @@ -1205,10 +1000,10 @@ describe('Configuration', () => { ); } ); - } - ); - } - ); + }); + } + ); + }); }); /** @@ -1267,9 +1062,7 @@ describe('Configuration', () => { (err, testSetEmptyStringConfig) => { assert.ok(!err); assert.strictEqual( - testSetEmptyStringConfig['oae-content'].storage[ - 'amazons3-access-key' - ], + testSetEmptyStringConfig['oae-content'].storage['amazons3-access-key'], 'blahblahblah' ); @@ -1288,9 +1081,7 @@ describe('Configuration', () => { (err, testSetEmptyStringConfig) => { assert.ok(!err); assert.strictEqual( - testSetEmptyStringConfig['oae-content'].storage[ - 'amazons3-access-key' - ], + testSetEmptyStringConfig['oae-content'].storage['amazons3-access-key'], '' ); @@ -1328,8 +1119,7 @@ describe('Configuration', () => { (err, config) => { assert.ok(!err); assert.strictEqual( - config['oae-authentication'].twitter - .enabled, + config['oae-authentication'].twitter.enabled, true ); @@ -1340,8 +1130,7 @@ describe('Configuration', () => { (err, config) => { assert.ok(!err); assert.strictEqual( - config['oae-authentication'].twitter - .enabled, + config['oae-authentication'].twitter.enabled, true ); @@ -1350,8 +1139,7 @@ describe('Configuration', () => { johnRestContext, null, { - 'oae-content/storage/amazons3-access-key': - 'moops' + 'oae-content/storage/amazons3-access-key': 'moops' }, err => { assert.ok(err); @@ -1380,10 +1168,7 @@ describe('Configuration', () => { }, err => { assert.ok(err); - assert.strictEqual( - err.code, - 401 - ); + assert.strictEqual(err.code, 401); // Ensure the value did not change RestAPI.Config.getTenantConfig( @@ -1392,10 +1177,9 @@ describe('Configuration', () => { (err, config) => { assert.ok(!err); assert.strictEqual( - config[ - 'oae-google-analytics' - ]['google-analytics'] - .globalEnabled, + config['oae-google-analytics'][ + 'google-analytics' + ].globalEnabled, false ); @@ -1417,9 +1201,7 @@ describe('Configuration', () => { (err, config) => { assert.ok(!err); assert.strictEqual( - config[ - 'oae-google-analytics' - ][ + config['oae-google-analytics'][ 'google-analytics' ].globalEnabled, true @@ -1434,13 +1216,8 @@ describe('Configuration', () => { // eslint-disable-next-line no-unused-vars config ) => { - assert.ok( - err - ); - assert.strictEqual( - err.code, - 400 - ); + assert.ok(err); + assert.strictEqual(err.code, 400); return callback(); } ); @@ -1489,152 +1266,116 @@ describe('Configuration', () => { */ it('verify internationalizable field', callback => { // Verify there is a default key - RestAPI.Config.getTenantConfig( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - (err, config) => { - assert.ok(!err); - assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); - assert.strictEqual(_.keys(config['oae-principals'].termsAndConditions.text).length, 1); - assert.strictEqual(config['oae-principals'].termsAndConditions.text.default, ''); + RestAPI.Config.getTenantConfig(globalAdminRestContext, global.oaeTests.tenants.cam.alias, (err, config) => { + assert.ok(!err); + assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); + assert.strictEqual(_.keys(config['oae-principals'].termsAndConditions.text).length, 1); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.default, ''); - // Set some American English text - ConfigTestUtil.updateConfigAndWait( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - { 'oae-principals/termsAndConditions/text/en_US': 'Some legalese in American English' }, - err => { + // Set some American English text + ConfigTestUtil.updateConfigAndWait( + globalAdminRestContext, + global.oaeTests.tenants.cam.alias, + { 'oae-principals/termsAndConditions/text/en_US': 'Some legalese in American English' }, + err => { + assert.ok(!err); + RestAPI.Config.getTenantConfig(globalAdminRestContext, global.oaeTests.tenants.cam.alias, (err, config) => { assert.ok(!err); - RestAPI.Config.getTenantConfig( + assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); + assert.strictEqual(_.keys(config['oae-principals'].termsAndConditions.text).length, 2); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.default, ''); + assert.strictEqual( + config['oae-principals'].termsAndConditions.text.en_US, + 'Some legalese in American English' + ); + + // Set some Dutch text + ConfigTestUtil.updateConfigAndWait( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - (err, config) => { + { + 'oae-principals/termsAndConditions/text/nl_BE': 'Een waterdicht legaal contract' + }, + err => { assert.ok(!err); - assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); - assert.strictEqual( - _.keys(config['oae-principals'].termsAndConditions.text).length, - 2 - ); - assert.strictEqual(config['oae-principals'].termsAndConditions.text.default, ''); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_US, - 'Some legalese in American English' - ); - - // Set some Dutch text - ConfigTestUtil.updateConfigAndWait( + RestAPI.Config.getTenantConfig( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - { - 'oae-principals/termsAndConditions/text/nl_BE': - 'Een waterdicht legaal contract' - }, - err => { + (err, config) => { assert.ok(!err); - RestAPI.Config.getTenantConfig( + assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); + assert.strictEqual(_.keys(config['oae-principals'].termsAndConditions.text).length, 3); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.default, ''); + assert.strictEqual( + config['oae-principals'].termsAndConditions.text.en_US, + 'Some legalese in American English' + ); + assert.strictEqual( + config['oae-principals'].termsAndConditions.text.nl_BE, + 'Een waterdicht legaal contract' + ); + + // Verify that updating the American English text doesn't change the other languages + ConfigTestUtil.updateConfigAndWait( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - (err, config) => { + { + 'oae-principals/termsAndConditions/text/en_US': 'Some updated legalese in American English' + }, + err => { assert.ok(!err); - assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); - assert.strictEqual( - _.keys(config['oae-principals'].termsAndConditions.text).length, - 3 - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.default, - '' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_US, - 'Some legalese in American English' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.nl_BE, - 'Een waterdicht legaal contract' - ); - - // Verify that updating the American English text doesn't change the other languages - ConfigTestUtil.updateConfigAndWait( + RestAPI.Config.getTenantConfig( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - { - 'oae-principals/termsAndConditions/text/en_US': - 'Some updated legalese in American English' - }, - err => { + (err, config) => { assert.ok(!err); - RestAPI.Config.getTenantConfig( + assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); + assert.strictEqual(_.keys(config['oae-principals'].termsAndConditions.text).length, 3); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.default, ''); + assert.strictEqual( + config['oae-principals'].termsAndConditions.text.en_US, + 'Some updated legalese in American English' + ); + assert.strictEqual( + config['oae-principals'].termsAndConditions.text.nl_BE, + 'Een waterdicht legaal contract' + ); + + // Verify that updating multiple keys does not affect the keys that should not be updated + const update = { + 'oae-principals/termsAndConditions/text/en_US': 'en us text', + 'oae-principals/termsAndConditions/text/fr_FR': 'fr fr text' + }; + ConfigTestUtil.updateConfigAndWait( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - (err, config) => { + update, + err => { assert.ok(!err); - assert.ok( - _.isObject(config['oae-principals'].termsAndConditions.text) - ); - assert.strictEqual( - _.keys(config['oae-principals'].termsAndConditions.text).length, - 3 - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.default, - '' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_US, - 'Some updated legalese in American English' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.nl_BE, - 'Een waterdicht legaal contract' - ); - - // Verify that updating multiple keys does not affect the keys that should not be updated - const update = { - 'oae-principals/termsAndConditions/text/en_US': 'en us text', - 'oae-principals/termsAndConditions/text/fr_FR': 'fr fr text' - }; - ConfigTestUtil.updateConfigAndWait( + RestAPI.Config.getTenantConfig( globalAdminRestContext, global.oaeTests.tenants.cam.alias, - update, - err => { + (err, config) => { assert.ok(!err); - RestAPI.Config.getTenantConfig( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - (err, config) => { - assert.ok(!err); - assert.ok( - _.isObject( - config['oae-principals'].termsAndConditions.text - ) - ); - assert.strictEqual( - _.keys(config['oae-principals'].termsAndConditions.text) - .length, - 4 - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text - .default, - '' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.en_US, - 'en us text' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.nl_BE, - 'Een waterdicht legaal contract' - ); - assert.strictEqual( - config['oae-principals'].termsAndConditions.text.fr_FR, - 'fr fr text' - ); - callback(); - } + assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); + assert.strictEqual( + _.keys(config['oae-principals'].termsAndConditions.text).length, + 4 + ); + assert.strictEqual(config['oae-principals'].termsAndConditions.text.default, ''); + assert.strictEqual( + config['oae-principals'].termsAndConditions.text.en_US, + 'en us text' + ); + assert.strictEqual( + config['oae-principals'].termsAndConditions.text.nl_BE, + 'Een waterdicht legaal contract' + ); + assert.strictEqual( + config['oae-principals'].termsAndConditions.text.fr_FR, + 'fr fr text' ); + callback(); } ); } @@ -1647,10 +1388,10 @@ describe('Configuration', () => { ); } ); - } - ); - } - ); + }); + } + ); + }); }); /** @@ -1697,10 +1438,7 @@ describe('Configuration', () => { global.oaeTests.tenants.cam.alias, (err, config) => { assert.ok(!err); - assert.strictEqual( - config['oae-principals'].recaptcha.publicKey, - 'untrimmed value' - ); + assert.strictEqual(config['oae-principals'].recaptcha.publicKey, 'untrimmed value'); // Set the recaptcha public key to only whitespace and ensure it is treated like the empty string ConfigTestUtil.updateConfigAndWait( @@ -1711,18 +1449,11 @@ describe('Configuration', () => { assert.ok(!err); // Ensure the recaptcha public key became the empty string - RestAPI.Config.getTenantConfig( - globalAdminRestContext, - null, - (err, config) => { - assert.ok(!err); - assert.strictEqual( - config['oae-principals'].recaptcha.publicKey, - '' - ); - return callback(); - } - ); + RestAPI.Config.getTenantConfig(globalAdminRestContext, null, (err, config) => { + assert.ok(!err); + assert.strictEqual(config['oae-principals'].recaptcha.publicKey, ''); + return callback(); + }); } ); } @@ -1751,12 +1482,9 @@ describe('Configuration', () => { }); // Assert that there is a value 'private' in the list - const privateValues = _.filter( - schema['oae-principals'].group.elements.visibility.list, - option => { - return option.value === 'private'; - } - ); + const privateValues = _.filter(schema['oae-principals'].group.elements.visibility.list, option => { + return option.value === 'private'; + }); assert.strictEqual(privateValues.length, 1); // Set a value for a list config field @@ -1766,33 +1494,29 @@ describe('Configuration', () => { { 'oae-principals/group/visibility': 'private' }, err => { assert.ok(!err); - RestAPI.Config.getTenantConfig( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - (err, config) => { - assert.ok(!err); - assert.strictEqual(config['oae-principals'].group.visibility, 'private'); + RestAPI.Config.getTenantConfig(globalAdminRestContext, global.oaeTests.tenants.cam.alias, (err, config) => { + assert.ok(!err); + assert.strictEqual(config['oae-principals'].group.visibility, 'private'); - // Clear the config and ensure it goes back to the default - ConfigTestUtil.clearConfigAndWait( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - ['oae-principals/group/visibility'], - err => { - assert.ok(!err); - RestAPI.Config.getTenantConfig( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - (err, config) => { - assert.ok(!err); - assert.strictEqual(config['oae-principals'].group.visibility, 'public'); - return callback(); - } - ); - } - ); - } - ); + // Clear the config and ensure it goes back to the default + ConfigTestUtil.clearConfigAndWait( + globalAdminRestContext, + global.oaeTests.tenants.cam.alias, + ['oae-principals/group/visibility'], + err => { + assert.ok(!err); + RestAPI.Config.getTenantConfig( + globalAdminRestContext, + global.oaeTests.tenants.cam.alias, + (err, config) => { + assert.ok(!err); + assert.strictEqual(config['oae-principals'].group.visibility, 'public'); + return callback(); + } + ); + } + ); + }); } ); }); @@ -1812,12 +1536,9 @@ describe('Configuration', () => { }); // Assert that there is a value 'amazons3' in the set - const amazons3Values = _.filter( - schema['oae-content'].storage.elements.backend.group, - option => { - return option.value === 'amazons3'; - } - ); + const amazons3Values = _.filter(schema['oae-content'].storage.elements.backend.group, option => { + return option.value === 'amazons3'; + }); assert.strictEqual(amazons3Values.length, 1); // Update one of the values @@ -1827,33 +1548,29 @@ describe('Configuration', () => { { 'oae-content/storage/backend': 'amazons3' }, err => { assert.ok(!err); - RestAPI.Config.getTenantConfig( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - (err, config) => { - assert.ok(!err); - assert.strictEqual(config['oae-content'].storage.backend, 'amazons3'); + RestAPI.Config.getTenantConfig(globalAdminRestContext, global.oaeTests.tenants.cam.alias, (err, config) => { + assert.ok(!err); + assert.strictEqual(config['oae-content'].storage.backend, 'amazons3'); - // Clear the config and ensure it goes back to the default - ConfigTestUtil.clearConfigAndWait( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - ['oae-content/storage/backend'], - err => { - assert.ok(!err); - RestAPI.Config.getTenantConfig( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - (err, config) => { - assert.ok(!err); - assert.strictEqual(config['oae-content'].storage.backend, 'local'); - return callback(); - } - ); - } - ); - } - ); + // Clear the config and ensure it goes back to the default + ConfigTestUtil.clearConfigAndWait( + globalAdminRestContext, + global.oaeTests.tenants.cam.alias, + ['oae-content/storage/backend'], + err => { + assert.ok(!err); + RestAPI.Config.getTenantConfig( + globalAdminRestContext, + global.oaeTests.tenants.cam.alias, + (err, config) => { + assert.ok(!err); + assert.strictEqual(config['oae-content'].storage.backend, 'local'); + return callback(); + } + ); + } + ); + }); } ); }); diff --git a/packages/oae-content/config/content.js b/packages/oae-content/config/content.js index ded102e460..3750583fc3 100644 --- a/packages/oae-content/config/content.js +++ b/packages/oae-content/config/content.js @@ -13,19 +13,36 @@ * permissions and limitations under the License. */ -const Fields = require('oae-config/lib/fields'); +import * as Fields from 'oae-config/lib/fields'; const PUBLIC = 'public'; const PRIVATE = 'private'; const LOGGEDIN = 'loggedin'; -module.exports = { - title: 'OAE Content Module', - visibility: { - name: 'Default Visibility Values', - description: 'Default visibility settings for new content', - elements: { - files: new Fields.List('Files Visibility', 'Default visibility for new files', PUBLIC, [ +export const title = 'OAE Content Module'; +export const visibility = { + name: 'Default Visibility Values', + description: 'Default visibility settings for new content', + elements: { + files: new Fields.List('Files Visibility', 'Default visibility for new files', PUBLIC, [ + { + name: 'Public', + value: PUBLIC + }, + { + name: 'Logged in users', + value: LOGGEDIN + }, + { + name: 'Private', + value: PRIVATE + } + ]), + collabdocs: new Fields.List( + 'Collaborative Document Visibility', + 'Default visibility for new Collaborative Documents', + PRIVATE, + [ { name: 'Public', value: PUBLIC @@ -38,46 +55,13 @@ module.exports = { name: 'Private', value: PRIVATE } - ]), - collabdocs: new Fields.List( - 'Collaborative Document Visibility', - 'Default visibility for new Collaborative Documents', - PRIVATE, - [ - { - name: 'Public', - value: PUBLIC - }, - { - name: 'Logged in users', - value: LOGGEDIN - }, - { - name: 'Private', - value: PRIVATE - } - ] - ), - collabsheets: new Fields.List( - 'Collaborative Spreadsheet Visibility', - 'Default visibility for new Collaborative Spreadsheets', - PRIVATE, - [ - { - name: 'Public', - value: PUBLIC - }, - { - name: 'Logged in users', - value: LOGGEDIN - }, - { - name: 'Private', - value: PRIVATE - } - ] - ), - links: new Fields.List('Links Visibility', 'Default visibility for new links', PUBLIC, [ + ] + ), + collabsheets: new Fields.List( + 'Collaborative Spreadsheet Visibility', + 'Default visibility for new Collaborative Spreadsheets', + PRIVATE, + [ { name: 'Public', value: PUBLIC @@ -90,7 +74,21 @@ module.exports = { name: 'Private', value: PRIVATE } - ]) - } + ] + ), + links: new Fields.List('Links Visibility', 'Default visibility for new links', PUBLIC, [ + { + name: 'Public', + value: PUBLIC + }, + { + name: 'Logged in users', + value: LOGGEDIN + }, + { + name: 'Private', + value: PRIVATE + } + ]) } }; diff --git a/packages/oae-content/config/storage.js b/packages/oae-content/config/storage.js index 79edf3ff31..e3936cfa1e 100644 --- a/packages/oae-content/config/storage.js +++ b/packages/oae-content/config/storage.js @@ -13,54 +13,49 @@ * permissions and limitations under the License. */ -const Fields = require('oae-config/lib/fields'); +import * as Fields from 'oae-config/lib/fields'; -module.exports = { - title: 'OAE Content Module', - storage: { - name: 'Storage backend settings', - description: 'Define the backend storage settings', - elements: { - backend: new Fields.Radio( - 'Default storage backend', - 'Default storage backend for file bodies', - 'local', - [ - { - name: 'Local', - value: 'local' - }, - { - name: 'Amazon S3', - value: 'amazons3' - } - ], - { tenantOverride: false, suppress: true, globalAdminOnly: true } - ), - 'amazons3-access-key': new Fields.Text( - 'Amazon Access Key', - 'Your Amazon Access key', - '', - { tenantOverride: false, suppress: true, globalAdminOnly: true } - ), - 'amazons3-secret-key': new Fields.Text( - 'Amazon Secret Key', - 'Your Amazon Secret key', - '', - { tenantOverride: false, suppress: true, globalAdminOnly: true } - ), - 'amazons3-region': new Fields.Text( - 'Amazon S3 Region', - 'The region for your S3 bucket', - 'us-east-1', - { tenantOverride: false, suppress: true, globalAdminOnly: true } - ), - 'amazons3-bucket': new Fields.Text( - 'Amazon S3 Bucket', - 'The Amazon S3 Bucket to store file bodies in', - 'oae-files', - { tenantOverride: false, suppress: true, globalAdminOnly: true } - ) - } +export const title = 'OAE Content Module'; +export const storage = { + name: 'Storage backend settings', + description: 'Define the backend storage settings', + elements: { + backend: new Fields.Radio( + 'Default storage backend', + 'Default storage backend for file bodies', + 'local', + [ + { + name: 'Local', + value: 'local' + }, + { + name: 'Amazon S3', + value: 'amazons3' + } + ], + { tenantOverride: false, suppress: true, globalAdminOnly: true } + ), + 'amazons3-access-key': new Fields.Text('Amazon Access Key', 'Your Amazon Access key', '', { + tenantOverride: false, + suppress: true, + globalAdminOnly: true + }), + 'amazons3-secret-key': new Fields.Text('Amazon Secret Key', 'Your Amazon Secret key', '', { + tenantOverride: false, + suppress: true, + globalAdminOnly: true + }), + 'amazons3-region': new Fields.Text('Amazon S3 Region', 'The region for your S3 bucket', 'us-east-1', { + tenantOverride: false, + suppress: true, + globalAdminOnly: true + }), + 'amazons3-bucket': new Fields.Text( + 'Amazon S3 Bucket', + 'The Amazon S3 Bucket to store file bodies in', + 'oae-files', + { tenantOverride: false, suppress: true, globalAdminOnly: true } + ) } }; diff --git a/packages/oae-content/lib/activity.js b/packages/oae-content/lib/activity.js index 8f5b219b3e..2e305e7dbe 100644 --- a/packages/oae-content/lib/activity.js +++ b/packages/oae-content/lib/activity.js @@ -13,23 +13,22 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); - -const ActivityAPI = require('oae-activity'); -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityModel = require('oae-activity/lib/model'); -const ActivityUtil = require('oae-activity/lib/util'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzUtil = require('oae-authz/lib/util'); -const MessageBoxAPI = require('oae-messagebox'); -const MessageBoxUtil = require('oae-messagebox/lib/util'); -const PrincipalsUtil = require('oae-principals/lib/util'); - -const ContentAPI = require('oae-content'); -const { ContentConstants } = require('oae-content/lib/constants'); -const ContentDAO = require('./internal/dao'); -const ContentUtil = require('./internal/util'); -const Etherpad = require('./internal/etherpad'); +import _ from 'underscore'; + +import * as ActivityAPI from 'oae-activity'; +import * as ActivityModel from 'oae-activity/lib/model'; +import * as ActivityUtil from 'oae-activity/lib/util'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as MessageBoxAPI from 'oae-messagebox'; +import * as MessageBoxUtil from 'oae-messagebox/lib/util'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; +import * as ContentAPI from 'oae-content'; +import { ActivityConstants } from 'oae-activity/lib/constants'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { ContentConstants } from 'oae-content/lib/constants'; +import * as Etherpad from './internal/etherpad'; +import * as ContentUtil from './internal/util'; +import * as ContentDAO from './internal/dao'; /// ///////////////// // CONTENT-CREATE // @@ -420,83 +419,74 @@ ActivityAPI.registerActivityType(ContentConstants.activity.ACTIVITY_CONTENT_UPDA /*! * Post a content-share or content-add-to-library activity based on content sharing */ -ContentAPI.emitter.on( - ContentConstants.events.UPDATED_CONTENT_MEMBERS, - (ctx, content, memberChangeInfo, opts) => { - if (opts.invitation) { - // If this member update came from an invitation, we bypass adding activity as there is a - // dedicated activity for that - return; - } - - const addedMemberIds = _.pluck(memberChangeInfo.members.added, 'id'); - const updatedMemberIds = _.pluck(memberChangeInfo.members.updated, 'id'); +ContentAPI.emitter.on(ContentConstants.events.UPDATED_CONTENT_MEMBERS, (ctx, content, memberChangeInfo, opts) => { + if (opts.invitation) { + // If this member update came from an invitation, we bypass adding activity as there is a + // dedicated activity for that + return; + } - const millis = Date.now(); - const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { - user: ctx.user() - }); - const contentResource = new ActivityModel.ActivitySeedResource('content', content.id, { - content - }); + const addedMemberIds = _.pluck(memberChangeInfo.members.added, 'id'); + const updatedMemberIds = _.pluck(memberChangeInfo.members.updated, 'id'); - // When a user is added, it is considered either a content-share or a content-add-to-library activity, depending on if the - // added user is the current user in context - _.each(addedMemberIds, memberId => { - if (memberId === ctx.user().id) { - // Users can't "share" with themselves, they actually "add it to their library" - ActivityAPI.postActivity( - ctx, - new ActivityModel.ActivitySeed( - ContentConstants.activity.ACTIVITY_CONTENT_ADD_TO_LIBRARY, - millis, - ActivityConstants.verbs.ADD, - actorResource, - contentResource - ) - ); - } else { - // A user shared content with some other user, fire the content share activity - const principalResourceType = PrincipalsUtil.isGroup(memberId) ? 'group' : 'user'; - const principalResource = new ActivityModel.ActivitySeedResource( - principalResourceType, - memberId - ); - ActivityAPI.postActivity( - ctx, - new ActivityModel.ActivitySeed( - ContentConstants.activity.ACTIVITY_CONTENT_SHARE, - millis, - ActivityConstants.verbs.SHARE, - actorResource, - contentResource, - principalResource - ) - ); - } - }); + const millis = Date.now(); + const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { + user: ctx.user() + }); + const contentResource = new ActivityModel.ActivitySeedResource('content', content.id, { + content + }); - // When a user's role is updated, we fire a "content-update-member-role" activity - _.each(updatedMemberIds, memberId => { - const principalResourceType = PrincipalsUtil.isGroup(memberId) ? 'group' : 'user'; - const principalResource = new ActivityModel.ActivitySeedResource( - principalResourceType, - memberId - ); + // When a user is added, it is considered either a content-share or a content-add-to-library activity, depending on if the + // added user is the current user in context + _.each(addedMemberIds, memberId => { + if (memberId === ctx.user().id) { + // Users can't "share" with themselves, they actually "add it to their library" ActivityAPI.postActivity( ctx, new ActivityModel.ActivitySeed( - ContentConstants.activity.ACTIVITY_CONTENT_UPDATE_MEMBER_ROLE, + ContentConstants.activity.ACTIVITY_CONTENT_ADD_TO_LIBRARY, millis, - ActivityConstants.verbs.UPDATE, + ActivityConstants.verbs.ADD, actorResource, - principalResource, contentResource ) ); - }); - } -); + } else { + // A user shared content with some other user, fire the content share activity + const principalResourceType = PrincipalsUtil.isGroup(memberId) ? 'group' : 'user'; + const principalResource = new ActivityModel.ActivitySeedResource(principalResourceType, memberId); + ActivityAPI.postActivity( + ctx, + new ActivityModel.ActivitySeed( + ContentConstants.activity.ACTIVITY_CONTENT_SHARE, + millis, + ActivityConstants.verbs.SHARE, + actorResource, + contentResource, + principalResource + ) + ); + } + }); + + // When a user's role is updated, we fire a "content-update-member-role" activity + _.each(updatedMemberIds, memberId => { + const principalResourceType = PrincipalsUtil.isGroup(memberId) ? 'group' : 'user'; + const principalResource = new ActivityModel.ActivitySeedResource(principalResourceType, memberId); + ActivityAPI.postActivity( + ctx, + new ActivityModel.ActivitySeed( + ContentConstants.activity.ACTIVITY_CONTENT_UPDATE_MEMBER_ROLE, + millis, + ActivityConstants.verbs.UPDATE, + actorResource, + principalResource, + contentResource + ) + ); + }); +}); /// //////////////////////// // ACTIVITY ENTITY TYPES // @@ -507,8 +497,7 @@ ContentAPI.emitter.on( * @see ActivityAPI#registerActivityEntityType */ const _contentProducer = function(resource, callback) { - const content = - resource.resourceData && resource.resourceData.content ? resource.resourceData.content : null; + const content = resource.resourceData && resource.resourceData.content ? resource.resourceData.content : null; // If the content item was fired with the resource, use it instead of fetching if (content) { @@ -579,9 +568,7 @@ const _contentTransformer = function(ctx, activityEntities, callback) { transformedActivityEntities[activityId] = transformedActivityEntities[activityId] || {}; _.each(entities, (entity, entityId) => { // Transform the persistent entity with its up-to-date preview status - transformedActivityEntities[activityId][ - entityId - ] = ContentUtil.transformPersistentContentActivityEntity( + transformedActivityEntities[activityId][entityId] = ContentUtil.transformPersistentContentActivityEntity( ctx, entity, previews[entity.content.latestRevisionId] @@ -658,12 +645,9 @@ const _contentCommentTransformer = function(ctx, activityEntities, callback) { const entity = activityEntities[activityId][entityId]; const contentId = entity.message.messageBoxId; const contentResource = AuthzUtil.getResourceFromId(contentId); - const profilePath = - '/content/' + contentResource.tenantAlias + '/' + contentResource.resourceId; + const profilePath = '/content/' + contentResource.tenantAlias + '/' + contentResource.resourceId; const urlFormat = '/api/content/' + contentId + '/messages/%s'; - transformedActivityEntities[activityId][ - entityId - ] = MessageBoxUtil.transformPersistentMessageActivityEntity( + transformedActivityEntities[activityId][entityId] = MessageBoxUtil.transformPersistentMessageActivityEntity( ctx, entity, profilePath, @@ -674,6 +658,7 @@ const _contentCommentTransformer = function(ctx, activityEntities, callback) { return callback(null, transformedActivityEntities); }; + /*! * Transform the comment persistent activity entities into OAE profiles * @see ActivityAPI#registerActivityEntityType @@ -700,11 +685,7 @@ ActivityAPI.registerActivityEntityType('content', { internal: _contentInternalTransformer }, propagation(associationsCtx, entity, callback) { - ActivityUtil.getStandardResourcePropagation( - entity.content.visibility, - AuthzConstants.joinable.NO, - callback - ); + ActivityUtil.getStandardResourcePropagation(entity.content.visibility, AuthzConstants.joinable.NO, callback); } }); @@ -726,91 +707,67 @@ ActivityAPI.registerActivityEntityType('content-comment', { /*! * Register an association that presents the content item */ -ActivityAPI.registerActivityEntityAssociation( - 'content', - 'self', - (associationsCtx, entity, callback) => { - return callback(null, [entity[ActivityConstants.properties.OAE_ID]]); - } -); +ActivityAPI.registerActivityEntityAssociation('content', 'self', (associationsCtx, entity, callback) => { + return callback(null, [entity[ActivityConstants.properties.OAE_ID]]); +}); /*! * Register an association that presents the members of a content item categorized by role */ -ActivityAPI.registerActivityEntityAssociation( - 'content', - 'members-by-role', - (associationsCtx, entity, callback) => { - ActivityUtil.getAllAuthzMembersByRole(entity[ActivityConstants.properties.OAE_ID], callback); - } -); +ActivityAPI.registerActivityEntityAssociation('content', 'members-by-role', (associationsCtx, entity, callback) => { + ActivityUtil.getAllAuthzMembersByRole(entity[ActivityConstants.properties.OAE_ID], callback); +}); /*! * Register an association that presents all the indirect members of a content item */ -ActivityAPI.registerActivityEntityAssociation( - 'content', - 'members', - (associationsCtx, entity, callback) => { - associationsCtx.get('members-by-role', (err, membersByRole) => { - if (err) { - return callback(err); - } +ActivityAPI.registerActivityEntityAssociation('content', 'members', (associationsCtx, entity, callback) => { + associationsCtx.get('members-by-role', (err, membersByRole) => { + if (err) { + return callback(err); + } - return callback(null, _.flatten(_.values(membersByRole))); - }); - } -); + return callback(null, _.flatten(_.values(membersByRole))); + }); +}); /*! * Register an association that presents all the managers of a content item */ -ActivityAPI.registerActivityEntityAssociation( - 'content', - 'managers', - (associationsCtx, entity, callback) => { - associationsCtx.get('members-by-role', (err, membersByRole) => { - if (err) { - return callback(err); - } +ActivityAPI.registerActivityEntityAssociation('content', 'managers', (associationsCtx, entity, callback) => { + associationsCtx.get('members-by-role', (err, membersByRole) => { + if (err) { + return callback(err); + } - return callback(null, membersByRole[AuthzConstants.role.MANAGER]); - }); - } -); + return callback(null, membersByRole[AuthzConstants.role.MANAGER]); + }); +}); /*! * Register an association that presents those users who are active on a collaborative document right now */ -ActivityAPI.registerActivityEntityAssociation( - 'content', - 'online-authors', - (associationsCtx, entity, callback) => { - // Ignore content items that aren't collaborative documents - if (entity.content.resourceSubType !== 'collabdoc') { - return callback(null, []); +ActivityAPI.registerActivityEntityAssociation('content', 'online-authors', (associationsCtx, entity, callback) => { + // Ignore content items that aren't collaborative documents + if (entity.content.resourceSubType !== 'collabdoc') { + return callback(null, []); + } + + // Grab the authors who are currently in the collaborative document + Etherpad.getOnlineAuthors(entity.content.id, entity.content.etherpadPadId, (err, onlineAuthorIds) => { + if (err) { + return callback(err); } - // Grab the authors who are currently in the collaborative document - Etherpad.getOnlineAuthors( - entity.content.id, - entity.content.etherpadPadId, - (err, onlineAuthorIds) => { - if (err) { - return callback(err); - } - - ContentDAO.Etherpad.getUserIds(onlineAuthorIds, (err, userIds) => { - if (err) { - return callback(err); - } - - return callback(null, _.values(userIds)); - }); + ContentDAO.Etherpad.getUserIds(onlineAuthorIds, (err, userIds) => { + if (err) { + return callback(err); } - ); - } -); + + return callback(null, _.values(userIds)); + }); + }); +}); /*! * Register an assocation that presents all the commenting contributors of a content item @@ -819,22 +776,13 @@ ActivityAPI.registerActivityEntityAssociation( 'content', 'message-contributors', (associationsCtx, entity, callback) => { - MessageBoxAPI.getRecentContributions( - entity[ActivityConstants.properties.OAE_ID], - null, - 100, - callback - ); + MessageBoxAPI.getRecentContributions(entity[ActivityConstants.properties.OAE_ID], null, 100, callback); } ); /*! * Register an association that presents the content item for a content-comment entity */ -ActivityAPI.registerActivityEntityAssociation( - 'content-comment', - 'self', - (associationsCtx, entity, callback) => { - return callback(null, [entity.contentId]); - } -); +ActivityAPI.registerActivityEntityAssociation('content-comment', 'self', (associationsCtx, entity, callback) => { + return callback(null, [entity.contentId]); +}); diff --git a/packages/oae-content/lib/api.js b/packages/oae-content/lib/api.js index 7be6133570..edb80d0c0b 100644 --- a/packages/oae-content/lib/api.js +++ b/packages/oae-content/lib/api.js @@ -13,40 +13,42 @@ * permissions and limitations under the License. */ - -const fs = require('fs'); -const path = require('path'); -const util = require('util'); -const ContentUtils = require('oae-content/lib/backends/util'); -const _ = require('underscore'); -const mime = require('mime'); -const ShortId = require('shortid'); - -const AuthzAPI = require('oae-authz'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzInvitations = require('oae-authz/lib/invitations'); -const AuthzPermissions = require('oae-authz/lib/permissions'); -const AuthzUtil = require('oae-authz/lib/util'); -const Config = require('oae-config').config('oae-content'); -const { Context } = require('oae-context'); -const EmitterAPI = require('oae-emitter'); -const LibraryAPI = require('oae-library'); -const log = require('oae-logger').logger('oae-content'); -const MessageBoxAPI = require('oae-messagebox'); -const { MessageBoxConstants } = require('oae-messagebox/lib/constants'); -const OaeUtil = require('oae-util/lib/util'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const PrincipalsUtil = require('oae-principals/lib/util'); -const ResourceActions = require('oae-resource/lib/actions'); -const Signature = require('oae-util/lib/signature'); -const { Validator } = require('oae-util/lib/validator'); - -const { ContentConstants } = require('./constants'); -const ContentDAO = require('./internal/dao'); -const ContentMembersLibrary = require('./internal/membersLibrary'); -const ContentUtil = require('./internal/util'); -const Ethercalc = require('./internal/ethercalc'); -const Etherpad = require('./internal/etherpad'); +import fs from 'fs'; +import path from 'path'; +import util from 'util'; +import * as ContentUtils from 'oae-content/lib/backends/util'; +import _ from 'underscore'; +import mime from 'mime'; +import ShortId from 'shortid'; + +import * as AuthzAPI from 'oae-authz'; +import * as AuthzInvitations from 'oae-authz/lib/invitations'; +import * as AuthzPermissions from 'oae-authz/lib/permissions'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import { setUpConfig } from 'oae-config'; +import * as EmitterAPI from 'oae-emitter'; +import * as LibraryAPI from 'oae-library'; +import { logger } from 'oae-logger'; + +import * as MessageBoxAPI from 'oae-messagebox'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; +import * as ResourceActions from 'oae-resource/lib/actions'; +import * as Signature from 'oae-util/lib/signature'; +import { MessageBoxConstants } from 'oae-messagebox/lib/constants'; +import { Context } from 'oae-context'; +import { Validator } from 'oae-util/lib/validator'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { ContentConstants } from './constants'; +import * as ContentDAO from './internal/dao'; +import * as ContentMembersLibrary from './internal/membersLibrary'; +import * as ContentUtil from './internal/util'; +import * as Ethercalc from './internal/ethercalc'; +import * as Etherpad from './internal/etherpad'; + +const Config = setUpConfig('oae-content'); +const log = logger('oae-content'); const COLLABDOC = 'collabdoc'; const COLLABSHEET = 'collabsheet'; @@ -2892,7 +2894,7 @@ const _generateRevisionId = function(contentId) { return AuthzUtil.toId('rev', tenantAlias, ShortId.generate()); }; -module.exports = { +export { getContent, getFullContentProfile, createLink, diff --git a/packages/oae-content/lib/backends/amazons3.js b/packages/oae-content/lib/backends/amazons3.js index c997850821..4c06ee407f 100644 --- a/packages/oae-content/lib/backends/amazons3.js +++ b/packages/oae-content/lib/backends/amazons3.js @@ -13,20 +13,24 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const Path = require('path'); -const util = require('util'); -const mime = require('mime'); -const { S3 } = require('awssum-amazon-s3'); +import fs from 'fs'; +import Path from 'path'; +import util from 'util'; +import mime from 'mime'; -const Config = require('oae-config').config('oae-content'); -const IO = require('oae-util/lib/io'); -const log = require('oae-logger').logger('amazon-storage'); -const TempFile = require('oae-util/lib/tempfile'); +import { S3 } from 'awssum-amazon-s3'; -const { ContentConstants } = require('../constants'); -const { DownloadStrategy } = require('../model'); -const BackendUtil = require('./util'); +import { setUpConfig } from 'oae-config'; +import * as IO from 'oae-util/lib/io'; +import { logger } from 'oae-logger'; +import * as TempFile from 'oae-util/lib/tempfile'; + +import { ContentConstants } from '../constants'; +import { DownloadStrategy } from '../model'; +import * as BackendUtil from './util'; + +const Config = setUpConfig('oae-content'); +const log = logger('amazon-storage'); /// /////////////////// // Storage methods. // @@ -75,6 +79,7 @@ const store = function(tenantAlias, file, options, callback) { log().error({ err }, 'Could not upload to S3.'); return callback(err); } + callback(null, 'amazons3:' + uri); }); }); @@ -146,6 +151,7 @@ const remove = function(tenantAlias, uri, callback) { log().error({ err }, 'Error removing %s', uriObj.location); return callback({ code: 500, msg: 'Unable to remove the file: ' + err }); } + callback(null); }); }; @@ -212,9 +218,4 @@ const _getClient = function(tenantAlias) { }); }; -module.exports = { - store, - get, - remove, - getDownloadStrategy -}; +export { store, get, remove, getDownloadStrategy }; diff --git a/packages/oae-content/lib/backends/interface.js b/packages/oae-content/lib/backends/interface.js index 2fa5c8c8ff..952002426a 100644 --- a/packages/oae-content/lib/backends/interface.js +++ b/packages/oae-content/lib/backends/interface.js @@ -31,7 +31,7 @@ * @param {Object} callback.err An error object (if any) * @param {String} callback.uri A URI that can be used to retrieve the filebody. */ - // eslint-disable-next-line no-unused-vars +// eslint-disable-next-line no-unused-vars const store = function(tenantAlias, file, options, callback) {}; /** @@ -45,7 +45,7 @@ const store = function(tenantAlias, file, options, callback) {}; * @param {Object} callback.err An error that occurred, if any * @param {TempFile} callback.file A tempfile that holds the data of the requested file. It's up to the callers to remove this file! */ - // eslint-disable-next-line no-unused-vars +// eslint-disable-next-line no-unused-vars const get = function(tenantAlias, uri, callback) {}; /** @@ -57,7 +57,7 @@ const get = function(tenantAlias, uri, callback) {}; * @param {Object} callback.err An error object (if any) * @param {TempFile} callback.file A tempfile that holds the data of the requested file. It's up to the callers to remove this file! */ - // eslint-disable-next-line no-unused-vars +// eslint-disable-next-line no-unused-vars const remove = function(tenantAlias, uri, callback) {}; /** @@ -67,12 +67,7 @@ const remove = function(tenantAlias, uri, callback) {}; * @param {Content} uri A URI that identifies the filebody * @return {DownloadStrategy} The download strategy that specifies how to download this resource */ - // eslint-disable-next-line no-unused-vars +// eslint-disable-next-line no-unused-vars const getDownloadStrategy = function(tenantAlias, uri) {}; -module.exports = { - store, - get, - remove, - getDownloadStrategy -}; +export { store, get, remove, getDownloadStrategy }; diff --git a/packages/oae-content/lib/backends/local.js b/packages/oae-content/lib/backends/local.js index 7c8af88512..54e5bc03fa 100644 --- a/packages/oae-content/lib/backends/local.js +++ b/packages/oae-content/lib/backends/local.js @@ -13,18 +13,20 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const Path = require('path'); -const util = require('util'); -const makeDir = require('make-dir'); +import fs from 'fs'; +import Path from 'path'; +import util from 'util'; +import makeDir from 'make-dir'; -const IO = require('oae-util/lib/io'); -const log = require('oae-logger').logger('local-storage'); -const TempFile = require('oae-util/lib/tempfile'); +import * as IO from 'oae-util/lib/io'; +import { logger } from 'oae-logger'; +import * as TempFile from 'oae-util/lib/tempfile'; -const { ContentConstants } = require('../constants'); -const { DownloadStrategy } = require('../model'); -const BackendUtil = require('./util'); +import { ContentConstants } from '../constants'; +import { DownloadStrategy } from '../model'; +import * as BackendUtil from './util'; + +const log = logger('local-storage'); let _rootDir = null; @@ -49,6 +51,7 @@ const init = function(rootDir, callback) { log().error({ dir: _rootDir, err }, 'Could not create/find the local storage directory'); return callback(err); } + callback(); }); }; @@ -137,6 +140,7 @@ const remove = function(tenantAlias, uri, callback) { // Otherwise we pass back an error } + if (err) { log().error({ err }, 'Error removing %s', path); return callback({ code: 500, msg: 'Unable to remove the file: ' + err }); @@ -179,11 +183,4 @@ const _ensureDirectoryExists = function(dir, callback) { }); }; -module.exports = { - init, - getRootDirectory, - store, - get, - remove, - getDownloadStrategy -}; +export { init, getRootDirectory, store, get, remove, getDownloadStrategy }; diff --git a/packages/oae-content/lib/backends/remote.js b/packages/oae-content/lib/backends/remote.js index 8933fb3582..3e1dc805f9 100644 --- a/packages/oae-content/lib/backends/remote.js +++ b/packages/oae-content/lib/backends/remote.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const { ContentConstants } = require('../constants'); -const { DownloadStrategy } = require('../model'); -const BackendUtil = require('./util'); +import { ContentConstants } from '../constants'; +import { DownloadStrategy } from '../model'; +import * as BackendUtil from './util'; /** * This backend isn't actually able to to store or retrieve anything. @@ -55,15 +55,7 @@ const remove = function(tenantAlias, uri, callback) { const getDownloadStrategy = function(tenantAlias, uri) { // The URI will look something like: remote:http://www.google.com. To get the target URL we simply strip out the // scheme portion of the URI - return new DownloadStrategy( - ContentConstants.backend.DOWNLOAD_STRATEGY_DIRECT, - BackendUtil.splitUri(uri).location - ); + return new DownloadStrategy(ContentConstants.backend.DOWNLOAD_STRATEGY_DIRECT, BackendUtil.splitUri(uri).location); }; -module.exports = { - store, - get, - remove, - getDownloadStrategy -}; +export { store, get, remove, getDownloadStrategy }; diff --git a/packages/oae-content/lib/backends/test.js b/packages/oae-content/lib/backends/test.js index 752986f8ef..039fdae3a7 100644 --- a/packages/oae-content/lib/backends/test.js +++ b/packages/oae-content/lib/backends/test.js @@ -22,10 +22,9 @@ * This should only be used during unit tests and *NEVER* in production. */ - -const { DownloadStrategy} = require('../model'); -const BackendUtil = require('./util'); -const LocalStorage = require('./local'); +import { DownloadStrategy } from '../model'; +import * as BackendUtil from './util'; +import * as LocalStorage from './local'; /** * @borrows Interface.store as TestStorageBackend.store @@ -54,6 +53,7 @@ const get = function(tenantAlias, uri, callback) { const remove = function(tenantAlias, uri, callback) { LocalStorage.remove(tenantAlias, uri, callback); }; + /** * @borrows Interface.getDownloadStrategy as TestStorageBackend.getDownloadStrategy */ @@ -62,9 +62,4 @@ const getDownloadStrategy = function(tenantAlias, uri) { return new DownloadStrategy('test', file); }; -module.exports = { - store, - get, - remove, - getDownloadStrategy -}; +export { store, get, remove, getDownloadStrategy }; diff --git a/packages/oae-content/lib/backends/util.js b/packages/oae-content/lib/backends/util.js index 8aadc31262..0c1e220be2 100644 --- a/packages/oae-content/lib/backends/util.js +++ b/packages/oae-content/lib/backends/util.js @@ -13,10 +13,9 @@ * permissions and limitations under the License. */ -const util = require('util'); -const ShortId = require('shortid'); - -const AuthzUtil = require('oae-authz/lib/util'); +import util from 'util'; +import ShortId from 'shortid'; +import * as AuthzUtil from 'oae-authz/lib/util'; const VALID_CHARACTERS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'; const COLLABDOC = 'collabdoc'; @@ -153,11 +152,4 @@ const isResourceAFile = function(resourceType) { return resourceType === FILE; }; -module.exports = { - splitUri, - generateUri, - isResourceACollabDoc, - isResourceACollabSheet, - isResourceAFile, - isResourceALink -}; +export { splitUri, generateUri, isResourceACollabDoc, isResourceACollabSheet, isResourceAFile, isResourceALink }; diff --git a/packages/oae-content/lib/constants.js b/packages/oae-content/lib/constants.js index a98874e0d8..1c683ca459 100644 --- a/packages/oae-content/lib/constants.js +++ b/packages/oae-content/lib/constants.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -const { AuthzConstants } = require('oae-authz/lib/constants'); +import { AuthzConstants } from 'oae-authz/lib/constants'; const ContentConstants = {}; -module.exports = { ContentConstants }; +export { ContentConstants }; ContentConstants.role = { // Determines not only all known roles, but the ordered priority they take as the "effective" diff --git a/packages/oae-content/lib/init.js b/packages/oae-content/lib/init.js index 16cbc172a1..fc6d941edc 100644 --- a/packages/oae-content/lib/init.js +++ b/packages/oae-content/lib/init.js @@ -13,20 +13,22 @@ * permissions and limitations under the License. */ -const mkdirp = require('mkdirp'); +import mkdirp from 'mkdirp'; -const Cleaner = require('oae-util/lib/cleaner'); -const log = require('oae-logger').logger('oae-content'); -const TaskQueue = require('oae-util/lib/taskqueue'); +import * as Cleaner from 'oae-util/lib/cleaner'; +import { logger } from 'oae-logger'; +import * as TaskQueue from 'oae-util/lib/taskqueue'; -const ContentAPI = require('./api'); -const { ContentConstants } = require('./constants'); -const ContentSearch = require('./search'); -const Etherpad = require('./internal/etherpad'); -const Ethercalc = require('./internal/ethercalc'); -const LocalStorage = require('./backends/local'); +import * as Etherpad from './internal/etherpad'; +import * as Ethercalc from './internal/ethercalc'; +import * as LocalStorage from './backends/local'; +import * as ContentAPI from './api'; +import { ContentConstants } from './constants'; +import * as ContentSearch from './search'; -module.exports = function(config, callback) { +const log = logger('oae-content'); + +export function init(config, callback) { // Initialize the content library capabilities // eslint-disable-next-line import/no-unassigned-import require('./library'); @@ -107,4 +109,4 @@ module.exports = function(config, callback) { }); }); }); -}; +} diff --git a/packages/oae-content/lib/internal/dao.content.js b/packages/oae-content/lib/internal/dao.content.js index 89c3f8a90b..68d78e30f6 100644 --- a/packages/oae-content/lib/internal/dao.content.js +++ b/packages/oae-content/lib/internal/dao.content.js @@ -13,26 +13,28 @@ * permissions and limitations under the License. */ -const { +import util from 'util'; +import { isResourceACollabDoc, isResourceACollabSheet, isResourceALink, isResourceAFile -} = require('oae-content/lib/backends/util'); +} from 'oae-content/lib/backends/util'; -const util = require('util'); -const _ = require('underscore'); +import _ from 'underscore'; -const AuthzAPI = require('oae-authz'); -const AuthzUtil = require('oae-authz/lib/util'); -const Cassandra = require('oae-util/lib/cassandra'); -const LibraryAPI = require('oae-library'); -const log = require('oae-logger').logger('content-dao'); -const OaeUtil = require('oae-util/lib/util'); +import * as AuthzAPI from 'oae-authz'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as LibraryAPI from 'oae-library'; +import { logger } from 'oae-logger'; +import * as OaeUtil from 'oae-util/lib/util'; -const { Content } = require('oae-content/lib/model'); -const { ContentConstants } = require('oae-content/lib/constants'); -const RevisionsDAO = require('./dao.revisions'); +import { Content } from 'oae-content/lib/model'; +import { ContentConstants } from 'oae-content/lib/constants'; +import * as RevisionsDAO from './dao.revisions'; + +const log = logger('content-dao'); /// //////////// // Retrieval // @@ -575,7 +577,7 @@ const _parsePreviews = function(hash) { } }; -module.exports = { +export { getContent, getMultipleContentItems, getAllContentMembers, diff --git a/packages/oae-content/lib/internal/dao.ethercalc.js b/packages/oae-content/lib/internal/dao.ethercalc.js index 5a7cbecc12..6291f52e5b 100644 --- a/packages/oae-content/lib/internal/dao.ethercalc.js +++ b/packages/oae-content/lib/internal/dao.ethercalc.js @@ -13,9 +13,11 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const Redis = require('oae-util/lib/redis'); -const log = require('oae-logger').logger('oae-ethercalc'); +import _ from 'underscore'; +import * as Redis from 'oae-util/lib/redis'; +import { logger } from 'oae-logger'; + +const log = logger('oae-ethercalc'); /** * Check whether a particular user has edited an Ethercalc spreadsheet @@ -115,4 +117,4 @@ const _getEditMappingKey = function(contentId) { return `ethercalc:edits:${contentId}`; }; -module.exports = { hasUserEditedSpreadsheet, setEditedBy }; +export { hasUserEditedSpreadsheet, setEditedBy }; diff --git a/packages/oae-content/lib/internal/dao.etherpad.js b/packages/oae-content/lib/internal/dao.etherpad.js index a5324a91ad..18416443e1 100644 --- a/packages/oae-content/lib/internal/dao.etherpad.js +++ b/packages/oae-content/lib/internal/dao.etherpad.js @@ -13,11 +13,13 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const log = require('oae-logger').logger('content-dao-etherpad'); -const Redis = require('oae-util/lib/redis'); +import { logger } from 'oae-logger'; +import * as Redis from 'oae-util/lib/redis'; + +const log = logger('content-dao-etherpad'); /** * Given a set of Etherpad author IDs, retrieve the corresponding OAE user ids @@ -35,10 +37,7 @@ const getUserIds = function(authorIds, callback) { const keys = _.map(authorIds, _getMappingKey); Redis.getClient().mget(keys, (err, userIds) => { if (err) { - log().error( - { err, authorIds }, - 'Failed to retrieve OAE users for a set of etherpad author ids' - ); + log().error({ err, authorIds }, 'Failed to retrieve OAE users for a set of etherpad author ids'); return callback({ code: 500, msg: 'Failed to retrieve OAE users for a set of etherpad author ids' @@ -81,7 +80,4 @@ const _getMappingKey = function(authorId) { return util.format('etherpad:mapping:%s', authorId); }; -module.exports = { - getUserIds, - saveAuthorId -}; +export { getUserIds, saveAuthorId }; diff --git a/packages/oae-content/lib/internal/dao.js b/packages/oae-content/lib/internal/dao.js index 152eeedc20..4150c19666 100644 --- a/packages/oae-content/lib/internal/dao.js +++ b/packages/oae-content/lib/internal/dao.js @@ -13,8 +13,10 @@ * permissions and limitations under the License. */ -module.exports.Content = require('./dao.content'); -module.exports.Etherpad = require('./dao.etherpad'); -module.exports.Ethercalc = require('./dao.ethercalc'); -module.exports.Previews = require('./dao.previews'); -module.exports.Revisions = require('./dao.revisions'); +import * as Content from './dao.content'; +import * as Etherpad from './dao.etherpad'; +import * as Ethercalc from './dao.ethercalc'; +import * as Previews from './dao.previews'; +import * as Revisions from './dao.revisions'; + +export { Content, Etherpad, Ethercalc, Previews, Revisions }; diff --git a/packages/oae-content/lib/internal/dao.previews.js b/packages/oae-content/lib/internal/dao.previews.js index 075e7c058e..7aea8c86fe 100644 --- a/packages/oae-content/lib/internal/dao.previews.js +++ b/packages/oae-content/lib/internal/dao.previews.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const Cassandra = require('oae-util/lib/cassandra'); -const { Validator } = require('oae-util/lib/validator'); +import * as Cassandra from 'oae-util/lib/cassandra'; +import { Validator } from 'oae-util/lib/validator'; /// //////////// // Retrieval // @@ -31,27 +31,23 @@ const { Validator } = require('oae-util/lib/validator'); * @param {Object[]} callback.previews The preview objects. */ const getContentPreviews = function(revisionId, callback) { - Cassandra.runQuery( - 'SELECT "name", "value" FROM "PreviewItems" WHERE "revisionId" = ?', - [revisionId], - (err, rows) => { - if (err) { - return callback(err); - } + Cassandra.runQuery('SELECT "name", "value" FROM "PreviewItems" WHERE "revisionId" = ?', [revisionId], (err, rows) => { + if (err) { + return callback(err); + } - const previews = _.map(rows, row => { - row = Cassandra.rowToHash(row); - row.value = row.value.split('#'); - return { - size: row.value[0], - uri: row.value[1], - filename: row.name - }; - }); + const previews = _.map(rows, row => { + row = Cassandra.rowToHash(row); + row.value = row.value.split('#'); + return { + size: row.value[0], + uri: row.value[1], + filename: row.name + }; + }); - return callback(null, previews); - } - ); + return callback(null, previews); + }); }; /** @@ -71,6 +67,7 @@ const getContentPreview = function(revisionId, previewItem, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback({ code: 404, @@ -254,29 +251,26 @@ const copyPreviewItems = function(fromRevisionId, toRevisionId, callback) { } // Select all the rows from the source revision preview items, then insert them into the destination revision preview items - Cassandra.runQuery( - 'SELECT * FROM "PreviewItems" WHERE "revisionId" = ?', - [fromRevisionId], - (err, rows) => { - if (err) { - return callback(err); - } - if (_.isEmpty(rows)) { - return callback(); - } - - // Copy the content over to the target revision id - const queries = _.map(rows, row => { - row = Cassandra.rowToHash(row); - return { - query: 'INSERT INTO "PreviewItems" ("revisionId", "name", "value") VALUES (?, ?, ?)', - parameters: [toRevisionId, row.name, row.value] - }; - }); + Cassandra.runQuery('SELECT * FROM "PreviewItems" WHERE "revisionId" = ?', [fromRevisionId], (err, rows) => { + if (err) { + return callback(err); + } - return Cassandra.runBatchQuery(queries, callback); + if (_.isEmpty(rows)) { + return callback(); } - ); + + // Copy the content over to the target revision id + const queries = _.map(rows, row => { + row = Cassandra.rowToHash(row); + return { + query: 'INSERT INTO "PreviewItems" ("revisionId", "name", "value") VALUES (?, ?, ?)', + parameters: [toRevisionId, row.name, row.value] + }; + }); + + return Cassandra.runBatchQuery(queries, callback); + }); }; /** @@ -320,13 +314,8 @@ const _getUriInFileData = function(fileData, size) { .slice(1) .join('#'); } + return null; }; -module.exports = { - getContentPreviews, - getContentPreview, - getPreviewUris, - storeMetadata, - copyPreviewItems -}; +export { getContentPreviews, getContentPreview, getPreviewUris, storeMetadata, copyPreviewItems }; diff --git a/packages/oae-content/lib/internal/dao.revisions.js b/packages/oae-content/lib/internal/dao.revisions.js index e85ec4c3ef..ad0d33c7a8 100644 --- a/packages/oae-content/lib/internal/dao.revisions.js +++ b/packages/oae-content/lib/internal/dao.revisions.js @@ -13,14 +13,14 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const Cassandra = require('oae-util/lib/cassandra'); -const OaeUtil = require('oae-util/lib/util'); +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as OaeUtil from 'oae-util/lib/util'; -const { Revision } = require('oae-content/lib/model'); -const ContentPreviewsDAO = require('./dao.previews'); +import { Revision } from 'oae-content/lib/model'; +import * as ContentPreviewsDAO from './dao.previews'; /// //////////// // Retrieval // @@ -55,6 +55,7 @@ const getRevisions = function(contentId, start, limit, opts, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback(null, []); } @@ -136,6 +137,7 @@ const getAllRevisionsForContent = function(contentIds, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback(null, {}); } @@ -178,20 +180,17 @@ const getAllRevisionsForContent = function(contentIds, callback) { * @param {Revision} callback.revision The retrieved revision */ const getRevision = function(revisionId, callback) { - Cassandra.runQuery( - 'SELECT * FROM "Revisions" WHERE "revisionId" = ?', - [revisionId], - (err, rows) => { - if (err) { - return callback(err); - } - if (_.isEmpty(rows)) { - return callback({ code: 404, msg: "Couldn't find revision: " + revisionId }); - } + Cassandra.runQuery('SELECT * FROM "Revisions" WHERE "revisionId" = ?', [revisionId], (err, rows) => { + if (err) { + return callback(err); + } - return callback(null, _rowToRevision(rows[0])); + if (_.isEmpty(rows)) { + return callback({ code: 404, msg: "Couldn't find revision: " + revisionId }); } - ); + + return callback(null, _rowToRevision(rows[0])); + }); }; /// //////////// @@ -241,10 +240,7 @@ const createRevision = function(revisionId, contentId, createdBy, revisionProper return callback(err); } - return callback( - null, - new Revision(contentId, revisionId, values.createdBy, values.created, values) - ); + return callback(null, new Revision(contentId, revisionId, values.createdBy, values.created, values)); } ); }); @@ -294,10 +290,4 @@ const _rowToRevision = function(row) { return new Revision(hash.contentId, hash.revisionId, hash.createdBy, hash.created, hash); }; -module.exports = { - getRevisions, - getMultipleRevisions, - getAllRevisionsForContent, - getRevision, - createRevision -}; +export { getRevisions, getMultipleRevisions, getAllRevisionsForContent, getRevision, createRevision }; diff --git a/packages/oae-content/lib/internal/ethercalc.js b/packages/oae-content/lib/internal/ethercalc.js index 6775989a45..f5c9fc1dc4 100644 --- a/packages/oae-content/lib/internal/ethercalc.js +++ b/packages/oae-content/lib/internal/ethercalc.js @@ -13,16 +13,17 @@ * permissions and limitations under the License. */ +import url from 'url'; import EthercalcClient from 'ethercalc-client'; -const url = require('url'); +import _ from 'underscore'; +import cheerio from 'cheerio'; -const _ = require('underscore'); -const cheerio = require('cheerio'); +import { logger } from 'oae-logger'; -const log = require('oae-logger').logger('ethercalc'); +import * as ContentDAO from './dao'; -const ContentDAO = require('./dao'); +const log = logger('ethercalc'); let ethercalcConfig = null; let ethercalc = null; @@ -351,7 +352,7 @@ const _isSCDocument = function(content) { return content.startsWith(SOCIAL_CALC_FORMAT_BEGIN_LINE) && content.endsWith(SOCIAL_CALC_FORMAT_END_LINE); }; -module.exports = { +export { refreshConfiguration, getConfig, createRoom, diff --git a/packages/oae-content/lib/internal/etherpad.js b/packages/oae-content/lib/internal/etherpad.js index a7bb1dbd95..81041d8283 100644 --- a/packages/oae-content/lib/internal/etherpad.js +++ b/packages/oae-content/lib/internal/etherpad.js @@ -13,13 +13,15 @@ * permissions and limitations under the License. */ -const url = require('url'); -const util = require('util'); -const _ = require('underscore'); -const cheerio = require('cheerio'); -const etherpad = require('etherpad-lite-client'); +import url from 'url'; +import util from 'util'; +import _ from 'underscore'; +import cheerio from 'cheerio'; -const log = require('oae-logger').logger('etherpad'); +import { logger } from 'oae-logger'; +import * as etherpad from 'etherpad-lite-client'; + +const log = logger('etherpad'); let etherpadServers = []; let etherpadConfig = null; @@ -83,10 +85,7 @@ const createPad = function(contentId, callback) { log().trace({ contentId }, 'Creating etherpad group'); client.createGroupIfNotExistsFor(args, (err, groupData) => { if (err) { - log().error( - { err, contentId, etherpad: client.options.host }, - 'Could not create an etherpad group' - ); + log().error({ err, contentId, etherpad: client.options.host }, 'Could not create an etherpad group'); return callback({ code: 500, msg: err.message }); } @@ -98,10 +97,7 @@ const createPad = function(contentId, callback) { log().trace({ contentId, groupID: groupData.groupID }, 'Creating etherpad group pad'); client.createGroupPad(groupPad, (err, padData) => { if (err) { - log().error( - { err, contentId, etherpad: client.options.host }, - 'Could not create an etherpad group pad' - ); + log().error({ err, contentId, etherpad: client.options.host }, 'Could not create an etherpad group pad'); return callback({ code: 500, msg: err.message }); } @@ -110,10 +106,7 @@ const createPad = function(contentId, callback) { etherpadGroupId: groupData.groupID, etherpadPadId: padData.padID }; - log().info( - { contentId, groupID: groupData.groupID, padID: padData.padID }, - 'Created an etherpad group and pad' - ); + log().info({ contentId, groupID: groupData.groupID, padID: padData.padID }, 'Created an etherpad group and pad'); callback(null, ids); }); }); @@ -162,10 +155,7 @@ const setHTML = function(contentId, padId, html, callback) { html = html || ''; html = _ensureHtmlDocument(html); } catch (error) { - log().error( - { err: error, html, contentId }, - 'Caught an error when trying to wrap an HTML fragment' - ); + log().error({ err: error, html, contentId }, 'Caught an error when trying to wrap an HTML fragment'); return callback({ code: 500, msg: 'Unable to set the etherpad HTML' }); } @@ -208,14 +198,14 @@ const joinPad = function(ctx, contentObj, callback) { const client = getClient(contentObj.id); /* - * Joining a pad consists out of three things: - * 1/ Mapping the OAE user to an etherpad author - * 2/ Creating a session for the etherpad author - * 3/ Returning a url to the UI. It should contain - * * The server etherpad is running on (ex: http://7.etherpad.oae.com/) - * * The pad URI (ex: /oae/c_cam_abc123) - * * The session ID (ex: ?sessionID=s.32b01f91d0e2c9a344) - */ + * Joining a pad consists out of three things: + * 1/ Mapping the OAE user to an etherpad author + * 2/ Creating a session for the etherpad author + * 3/ Returning a url to the UI. It should contain + * * The server etherpad is running on (ex: http://7.etherpad.oae.com/) + * * The pad URI (ex: /oae/c_cam_abc123) + * * The session ID (ex: ?sessionID=s.32b01f91d0e2c9a344) + */ const args = { authorMapper: ctx.user().id, name: ctx.user().displayName @@ -345,6 +335,7 @@ const isContentEqual = function(one, other) { if (one === other) { return true; } + if (!one || !other) { return false; } @@ -474,10 +465,11 @@ const _hash = function(str, nr) { for (let i = 0; i < str.length; i++) { code += str.charCodeAt(i); } + return code % nr; }; -module.exports = { +export { refreshConfiguration, getConfig, createPad, diff --git a/packages/oae-content/lib/internal/membersLibrary.js b/packages/oae-content/lib/internal/membersLibrary.js index f4f6c8e158..0149e27603 100644 --- a/packages/oae-content/lib/internal/membersLibrary.js +++ b/packages/oae-content/lib/internal/membersLibrary.js @@ -15,12 +15,14 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const LibraryAPI = require('oae-library'); +import * as LibraryAPI from 'oae-library'; -const log = require('oae-logger').logger('content-memberslibrary'); -const { ContentConstants } = require('../constants'); +import { logger } from 'oae-logger'; +import { ContentConstants } from '../constants'; + +const log = logger('content-memberslibrary'); /** * Get items from the content member's library @@ -147,8 +149,4 @@ const _remove = function(contentItem, principalIds, callback) { LibraryAPI.Index.remove(ContentConstants.library.MEMBERS_LIBRARY_INDEX_NAME, entries, callback); }; -module.exports = { - list, - insert, - remove -}; +export { list, insert, remove }; diff --git a/packages/oae-content/lib/internal/util.js b/packages/oae-content/lib/internal/util.js index 2ffdfa4295..5a3a600485 100644 --- a/packages/oae-content/lib/internal/util.js +++ b/packages/oae-content/lib/internal/util.js @@ -13,20 +13,23 @@ * permissions and limitations under the License. */ -const querystring = require('querystring'); -const util = require('util'); -const _ = require('underscore'); +import querystring from 'querystring'; +import util from 'util'; +import { setUpConfig } from 'oae-config'; +import _ from 'underscore'; +import { ContentConstants } from 'oae-content/lib/constants'; -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityModel = require('oae-activity/lib/model'); -const PreviewConstants = require('oae-preview-processor/lib/constants'); -const Signature = require('oae-util/lib/signature'); -const TenantsUtil = require('oae-tenants/lib/util'); +import { logger } from 'oae-logger'; -const ContentConfig = require('oae-config').config('oae-content'); -const { ContentConstants } = require('oae-content/lib/constants'); +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as ActivityModel from 'oae-activity/lib/model'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; +import * as Signature from 'oae-util/lib/signature'; +import * as TenantsUtil from 'oae-tenants/lib/util'; -const log = require('oae-logger').logger('oae-content-util'); +const ContentConfig = setUpConfig('oae-content'); + +const log = logger('oae-content-util'); const TIME_1_WEEK_IN_SECONDS = 7 * 24 * 60 * 60; @@ -78,52 +81,27 @@ const augmentContent = function(ctx, content, duration, offset) { // Replace all the different sizes of back-end image URIs to signed URLs the consumer can use if (content.previews) { if (content.previews.thumbnailUri) { - content.previews.thumbnailUrl = getSignedDownloadUrl( - ctx, - content.previews.thumbnailUri, - duration, - offset - ); + content.previews.thumbnailUrl = getSignedDownloadUrl(ctx, content.previews.thumbnailUri, duration, offset); delete content.previews.thumbnailUri; } if (content.previews.smallUri) { - content.previews.smallUrl = getSignedDownloadUrl( - ctx, - content.previews.smallUri, - duration, - offset - ); + content.previews.smallUrl = getSignedDownloadUrl(ctx, content.previews.smallUri, duration, offset); delete content.previews.smallUri; } if (content.previews.mediumUri) { - content.previews.mediumUrl = getSignedDownloadUrl( - ctx, - content.previews.mediumUri, - duration, - offset - ); + content.previews.mediumUrl = getSignedDownloadUrl(ctx, content.previews.mediumUri, duration, offset); delete content.previews.mediumUri; } if (content.previews.largeUri) { - content.previews.largeUrl = getSignedDownloadUrl( - ctx, - content.previews.largeUri, - duration, - offset - ); + content.previews.largeUrl = getSignedDownloadUrl(ctx, content.previews.largeUri, duration, offset); delete content.previews.largeUri; } if (content.previews.wideUri) { - content.previews.wideUrl = getSignedDownloadUrl( - ctx, - content.previews.wideUri, - duration, - offset - ); + content.previews.wideUrl = getSignedDownloadUrl(ctx, content.previews.wideUri, duration, offset); delete content.previews.wideUri; } } @@ -155,9 +133,7 @@ const getSignedDownloadUrl = function(ctx, uri, duration, offset) { // All we sign for the download url is the URI const data = { uri }; const signatureData = - duration === -1 - ? { signature: Signature.sign(data) } - : Signature.createExpiringSignature(data, duration, offset); + duration === -1 ? { signature: Signature.sign(data) } : Signature.createExpiringSignature(data, duration, offset); // Attach the signature and expiry time to the final data object _.extend(data, signatureData); @@ -177,10 +153,9 @@ const getSignedDownloadUrl = function(ctx, uri, duration, offset) { */ const verifySignedDownloadQueryString = function(qs) { if (qs.expires) { - return Signature.verifyExpiringSignature({ uri: qs.uri }, qs.expires, qs.signature) - ? qs.uri - : null; + return Signature.verifyExpiringSignature({ uri: qs.uri }, qs.expires, qs.signature) ? qs.uri : null; } + return Signature.verify({ uri: qs.uri }, qs.signature) ? qs.uri : null; }; @@ -271,7 +246,7 @@ const transformPersistentContentActivityEntityToInternal = function(ctx, entity, return content; }; -module.exports = { +export { getStorageBackend, augmentContent, getSignedDownloadUrl, diff --git a/packages/oae-content/lib/invitations.js b/packages/oae-content/lib/invitations.js index 99f04c7fbe..cd51f0bbf4 100644 --- a/packages/oae-content/lib/invitations.js +++ b/packages/oae-content/lib/invitations.js @@ -13,20 +13,22 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const AuthzInvitationsDAO = require('oae-authz/lib/invitations/dao'); -const AuthzUtil = require('oae-authz/lib/util'); -const { Context } = require('oae-context'); -const { Invitation} = require('oae-authz/lib/invitations/model'); -const ResourceActions = require('oae-resource/lib/actions'); -const { ResourceConstants} = require('oae-resource/lib/constants'); +import * as AuthzInvitationsDAO from 'oae-authz/lib/invitations/dao'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import { Context } from 'oae-context'; +import { Invitation } from 'oae-authz/lib/invitations/model'; +import * as ResourceActions from 'oae-resource/lib/actions'; +import { ResourceConstants } from 'oae-resource/lib/constants'; -const ContentAPI = require('oae-content'); -const { ContentConstants} = require('oae-content/lib/constants'); -const ContentDAO = require('oae-content/lib/internal/dao'); +import * as ContentAPI from 'oae-content'; +import { ContentConstants } from 'oae-content/lib/constants'; +import * as ContentDAO from 'oae-content/lib/internal/dao'; -const log = require('oae-logger').logger('oae-content-invitations'); +import { logger } from 'oae-logger'; + +const log = logger('oae-content-invitations'); /*! * When an invitation is accepted, pass on the events to update content members and then feed back @@ -84,24 +86,21 @@ ResourceActions.emitter.when( /*! * When content is deleted, delete all its invitations as well */ -ContentAPI.emitter.when( - ContentConstants.events.DELETED_CONTENT, - (ctx, content, members, callback) => { - AuthzInvitationsDAO.deleteInvitationsByResourceId(content.id, err => { - if (err) { - log().warn( - { - err, - contentId: content.id - }, - 'An error occurred while removing invitations after a content item was deleted' - ); - } +ContentAPI.emitter.when(ContentConstants.events.DELETED_CONTENT, (ctx, content, members, callback) => { + AuthzInvitationsDAO.deleteInvitationsByResourceId(content.id, err => { + if (err) { + log().warn( + { + err, + contentId: content.id + }, + 'An error occurred while removing invitations after a content item was deleted' + ); + } - return callback(); - }); - } -); + return callback(); + }); +}); /** * Determine if the given id is a content id diff --git a/packages/oae-content/lib/library.js b/packages/oae-content/lib/library.js index c6ad3a1c2c..acde234a97 100644 --- a/packages/oae-content/lib/library.js +++ b/packages/oae-content/lib/library.js @@ -13,17 +13,18 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const AuthzAPI = require('oae-authz'); -const LibraryAPI = require('oae-library'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); +import _ from 'underscore'; +import * as AuthzAPI from 'oae-authz'; +import * as LibraryAPI from 'oae-library'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as ContentAPI from 'oae-content'; +import * as ContentDAO from 'oae-content/lib/internal/dao'; +import * as ContentMembersLibrary from 'oae-content/lib/internal/membersLibrary'; -const ContentAPI = require('oae-content'); -const { ContentConstants} = require('oae-content/lib/constants'); -const ContentDAO = require('oae-content/lib/internal/dao'); -const ContentMembersLibrary = require('oae-content/lib/internal/membersLibrary'); +import { ContentConstants } from 'oae-content/lib/constants'; +import { logger } from 'oae-logger'; -const log = require('oae-logger').logger('oae-content-library'); +const log = logger('oae-content-library'); /*! * Register a library indexer that can provide resources to reindex the content library @@ -31,41 +32,35 @@ const log = require('oae-logger').logger('oae-content-library'); LibraryAPI.Index.registerLibraryIndex(ContentConstants.library.CONTENT_LIBRARY_INDEX_NAME, { pageResources(libraryId, start, limit, callback) { // Query all the content ids ('c') to which the library owner is directly associated in this batch of paged resources - AuthzAPI.getRolesForPrincipalAndResourceType( - libraryId, - 'c', - start, - limit, - (err, roles, nextToken) => { - if (err) { - return callback(err); - } + AuthzAPI.getRolesForPrincipalAndResourceType(libraryId, 'c', start, limit, (err, roles, nextToken) => { + if (err) { + return callback(err); + } + + // We just need the ids, not the roles + const ids = _.pluck(roles, 'id'); - // We just need the ids, not the roles - const ids = _.pluck(roles, 'id'); - - // Get the properties of the content items in the library that are relevant to building the library - ContentDAO.Content.getMultipleContentItems( - ids, - ['contentId', 'tenantAlias', 'visibility', 'lastModified'], - (err, contentItems) => { - if (err) { - return callback(err); - } - - // Map the content items to light-weight resources with just the properties needed to populate the library index - const resources = _.chain(contentItems) - .compact() - .map(content => { - return { rank: content.lastModified, resource: content }; - }) - .value(); - - return callback(null, resources, nextToken); + // Get the properties of the content items in the library that are relevant to building the library + ContentDAO.Content.getMultipleContentItems( + ids, + ['contentId', 'tenantAlias', 'visibility', 'lastModified'], + (err, contentItems) => { + if (err) { + return callback(err); } - ); - } - ); + + // Map the content items to light-weight resources with just the properties needed to populate the library index + const resources = _.chain(contentItems) + .compact() + .map(content => { + return { rank: content.lastModified, resource: content }; + }) + .value(); + + return callback(null, resources, nextToken); + } + ); + }); } }); @@ -80,21 +75,17 @@ LibraryAPI.Index.registerLibraryIndex(ContentConstants.library.MEMBERS_LIBRARY_I } const ids = _.pluck(memberInfos, 'id'); - PrincipalsDAO.getPrincipals( - ids, - ['principalId', 'tenantAlias', 'visibility'], - (err, memberProfiles) => { - if (err) { - return callback(err); - } + PrincipalsDAO.getPrincipals(ids, ['principalId', 'tenantAlias', 'visibility'], (err, memberProfiles) => { + if (err) { + return callback(err); + } - const resources = _.map(memberProfiles, memberProfile => { - return { resource: memberProfile }; - }); + const resources = _.map(memberProfiles, memberProfile => { + return { resource: memberProfile }; + }); - return callback(null, resources, nextToken); - } - ); + return callback(null, resources, nextToken); + }); }); } }); diff --git a/packages/oae-content/lib/migration.js b/packages/oae-content/lib/migration.js index b98918f986..6eae1bd79f 100644 --- a/packages/oae-content/lib/migration.js +++ b/packages/oae-content/lib/migration.js @@ -1,5 +1,5 @@ -const async = require('async'); -const Cassandra = require('oae-util/lib/cassandra'); +import async from 'async'; +import { createColumnFamilies, runQuery } from 'oae-util/lib/cassandra'; /** * Ensure that the all of the content-related schemas are created. If they already exist, this method will not do anything @@ -9,7 +9,7 @@ const Cassandra = require('oae-util/lib/cassandra'); * @api private */ const ensureSchema = function(callback) { - Cassandra.createColumnFamilies( + createColumnFamilies( { Content: 'CREATE TABLE "Content" ("contentId" text PRIMARY KEY, "tenantAlias" text, "visibility" text, "displayName" text, "description" text, "resourceSubType" text, "createdBy" text, "created" text, "lastModified" text, "latestRevisionId" text, "uri" text, "previews" text, "status" text, "largeUri" text, "mediumUri" text, "smallUri" text, "thumbnailUri" text, "wideUri" text, "etherpadGroupId" text, "etherpadPadId" text, "filename" text, "link" text, "mime" text, "size" text)', @@ -29,7 +29,7 @@ const ensureSchema = function(callback) { async.eachSeries( queries, (eachQuery, done) => { - Cassandra.runQuery(eachQuery.cql, eachQuery.parameters, done); + runQuery(eachQuery.cql, eachQuery.parameters, done); }, () => { callback(); @@ -39,4 +39,4 @@ const ensureSchema = function(callback) { ); }; -module.exports = { ensureSchema }; +export { ensureSchema }; diff --git a/packages/oae-content/lib/model.js b/packages/oae-content/lib/model.js index 33530a0eb1..2d52ae75ed 100644 --- a/packages/oae-content/lib/model.js +++ b/packages/oae-content/lib/model.js @@ -13,11 +13,11 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const TenantsAPI = require('oae-tenants'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as TenantsAPI from 'oae-tenants'; /// //////// // Model // @@ -140,8 +140,4 @@ const _getDownloadPath = function(contentId, revisionId) { return util.format('/api/content/%s/download/%s', contentId, revisionId); }; -module.exports = { - Content, - Revision, - DownloadStrategy -}; +export { Content, Revision, DownloadStrategy }; diff --git a/packages/oae-content/lib/previews.js b/packages/oae-content/lib/previews.js index 65f61e0a25..757e557cb7 100644 --- a/packages/oae-content/lib/previews.js +++ b/packages/oae-content/lib/previews.js @@ -13,27 +13,24 @@ * permissions and limitations under the License. */ -const PreviewProcessorAPI = require('oae-preview-processor'); +import * as PreviewProcessorAPI from 'oae-preview-processor'; -const { ContentConstants } = require('oae-content/lib/constants'); -const ContentAPI = require('./api'); +import { ContentConstants } from 'oae-content/lib/constants'; +import * as ContentAPI from './api'; ContentAPI.emitter.on(ContentConstants.events.CREATED_CONTENT, (ctx, content, revision) => { PreviewProcessorAPI.submitForProcessing(content.id, revision.revisionId); }); -ContentAPI.emitter.on( - ContentConstants.events.UPDATED_CONTENT, - (ctx, newContentObj, oldContentObj) => { - /* - * This event gets emitted when the content metadata gets updated. - * We only need to check links here. - */ - if (newContentObj.resourceSubType === 'link' && newContentObj.link !== oldContentObj.link) { - PreviewProcessorAPI.submitForProcessing(newContentObj.id, oldContentObj.latestRevisionId); - } +ContentAPI.emitter.on(ContentConstants.events.UPDATED_CONTENT, (ctx, newContentObj, oldContentObj) => { + /* + * This event gets emitted when the content metadata gets updated. + * We only need to check links here. + */ + if (newContentObj.resourceSubType === 'link' && newContentObj.link !== oldContentObj.link) { + PreviewProcessorAPI.submitForProcessing(newContentObj.id, oldContentObj.latestRevisionId); } -); +}); // A collaborative document gets published or a new file body gets uploaded. ContentAPI.emitter.on( diff --git a/packages/oae-content/lib/rest.js b/packages/oae-content/lib/rest.js index 96652e5201..5b4cb91e62 100644 --- a/packages/oae-content/lib/rest.js +++ b/packages/oae-content/lib/rest.js @@ -13,22 +13,22 @@ * permissions and limitations under the License. */ -const { +import * as querystring from 'querystring'; +import { isResourceACollabDoc, isResourceACollabSheet, isResourceAFile, isResourceALink -} = require('oae-content/lib/backends/util'); +} from 'oae-content/lib/backends/util'; -const querystring = require('querystring'); -const _ = require('underscore'); +import _ from 'underscore'; -const { AuthzConstants } = require('oae-authz/lib/constants'); -const OAE = require('oae-util/lib/oae'); -const OaeUtil = require('oae-util/lib/util'); +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as OAE from 'oae-util/lib/oae'; +import * as OaeUtil from 'oae-util/lib/util'; -const ContentAPI = require('./api'); -const { ContentConstants } = require('./constants'); +import * as ContentAPI from './api'; +import { ContentConstants } from './constants'; /** * Verify the signature information provided by a signed download request and diff --git a/packages/oae-content/lib/search.js b/packages/oae-content/lib/search.js index a05de6f7de..ebc09c0994 100644 --- a/packages/oae-content/lib/search.js +++ b/packages/oae-content/lib/search.js @@ -13,23 +13,25 @@ * permissions and limitations under the License. */ -const { isResourceACollabDoc, isResourceACollabSheet } = require('oae-content/lib/backends/util'); - -const fs = require('fs'); -const util = require('util'); -const _ = require('underscore'); - -const AuthzUtil = require('oae-authz/lib/util'); -const log = require('oae-logger').logger('content-search'); -const MessageBoxSearch = require('oae-messagebox/lib/search'); -const SearchAPI = require('oae-search'); -const SearchUtil = require('oae-search/lib/util'); -const TenantsAPI = require('oae-tenants'); - -const ContentAPI = require('oae-content'); -const { ContentConstants } = require('oae-content/lib/constants'); -const ContentDAO = require('oae-content/lib/internal/dao'); -const ContentUtil = require('oae-content/lib/internal/util'); +import fs from 'fs'; +import util from 'util'; +import { isResourceACollabDoc, isResourceACollabSheet } from 'oae-content/lib/backends/util'; +import _ from 'underscore'; + +import * as AuthzUtil from 'oae-authz/lib/util'; +import { logger } from 'oae-logger'; +import * as MessageBoxSearch from 'oae-messagebox/lib/search'; +import * as SearchAPI from 'oae-search'; +import * as SearchUtil from 'oae-search/lib/util'; +import * as TenantsAPI from 'oae-tenants'; + +import * as ContentAPI from 'oae-content'; +import * as ContentDAO from 'oae-content/lib/internal/dao'; +import * as ContentUtil from 'oae-content/lib/internal/util'; +import { ContentConstants } from 'oae-content/lib/constants'; +import * as contentBodySchema from './search/schema/contentBodySchema'; + +const log = logger('content-search'); /** * Initializes the child search documents for the Content module @@ -37,10 +39,10 @@ const ContentUtil = require('oae-content/lib/internal/util'); * @param {Function} callback Standard callback function * @param {Object} callback.err An error that occurred, if any */ -module.exports.init = function(callback) { +export const init = function(callback) { const contentBodyChildSearchDocumentOptions = { resourceTypes: ['content'], - schema: require('./search/schema/contentBodySchema'), + schema: contentBodySchema, producer(resources, callback) { return _produceContentBodyDocuments(resources.slice(), callback); } diff --git a/packages/oae-content/lib/search/schema/contentBodySchema.js b/packages/oae-content/lib/search/schema/contentBodySchema.js index 0b2148b7eb..7aad312b7d 100644 --- a/packages/oae-content/lib/search/schema/contentBodySchema.js +++ b/packages/oae-content/lib/search/schema/contentBodySchema.js @@ -22,11 +22,9 @@ * {String} schema.body A free-text string representing the body of the content */ /* eslint-disable unicorn/filename-case */ -module.exports = { - body: { - type: 'string', - store: 'no', - index: 'analyzed', - analyzer: 'text_content' - } +export const body = { + type: 'string', + store: 'no', + index: 'analyzed', + analyzer: 'text_content' }; diff --git a/packages/oae-content/lib/test/util.js b/packages/oae-content/lib/test/util.js index 35ed9031b0..be90838d12 100644 --- a/packages/oae-content/lib/test/util.js +++ b/packages/oae-content/lib/test/util.js @@ -13,21 +13,21 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const util = require('util'); -const _ = require('underscore'); -const ShortId = require('shortid'); -const async = require('async'); - -const AuthzTestUtil = require('oae-authz/lib/test/util'); -const MqTestUtil = require('oae-util/lib/test/mq-util'); -const LibraryTestUtil = require('oae-library/lib/test/util'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); -const RestAPI = require('oae-rest'); -const TaskQueue = require('oae-util/lib/taskqueue'); -const TestsUtil = require('oae-tests/lib/util'); - -const { ContentConstants } = require('oae-content/lib/constants'); +import assert from 'assert'; +import util from 'util'; +import _ from 'underscore'; +import ShortId from 'shortid'; +import async from 'async'; + +import * as AuthzTestUtil from 'oae-authz/lib/test/util'; +import * as MqTestUtil from 'oae-util/lib/test/mq-util'; +import * as LibraryTestUtil from 'oae-library/lib/test/util'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TaskQueue from 'oae-util/lib/taskqueue'; +import * as TestsUtil from 'oae-tests/lib/util'; + +import { ContentConstants } from 'oae-content/lib/constants'; /** * Set up 2 public tenants and 2 private tenants, each with a public, loggedin, private set of users, groups and @@ -932,7 +932,7 @@ const _purgeMembersLibrary = function(contentId, callback) { LibraryTestUtil.assertPurgeFreshLibraries(ContentConstants.library.MEMBERS_LIBRARY_INDEX_NAME, [contentId], callback); }; -module.exports = { +export { setupMultiTenantPrivacyEntities, assertCreateLinkSucceeds, assertCreateLinkFails, diff --git a/packages/oae-content/tests/test-activity.js b/packages/oae-content/tests/test-activity.js index 4b031ea957..8255d9766f 100644 --- a/packages/oae-content/tests/test-activity.js +++ b/packages/oae-content/tests/test-activity.js @@ -13,28 +13,29 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const url = require('url'); -const util = require('util'); -const _ = require('underscore'); - -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityTestsUtil = require('oae-activity/lib/test/util'); -const ActivityDAO = require('oae-activity/lib/internal/dao'); -const AuthzUtil = require('oae-authz/lib/util'); -const Cassandra = require('oae-util/lib/cassandra'); -const EmailTestsUtil = require('oae-email/lib/test/util'); -const FollowingTestsUtil = require('oae-following/lib/test/util'); -const PreviewConstants = require('oae-preview-processor/lib/constants'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); -const RestAPI = require('oae-rest'); -const RestUtil = require('oae-rest/lib/util'); -const TestsUtil = require('oae-tests'); - -const ContentTestUtil = require('oae-content/lib/test/util'); -const Etherpad = require('oae-content/lib/internal/etherpad'); +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import url from 'url'; +import util from 'util'; +import _ from 'underscore'; + +import { ActivityConstants } from 'oae-activity/lib/constants'; + +import * as ActivityTestsUtil from 'oae-activity/lib/test/util'; +import * as ActivityDAO from 'oae-activity/lib/internal/dao'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as EmailTestsUtil from 'oae-email/lib/test/util'; +import * as FollowingTestsUtil from 'oae-following/lib/test/util'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as RestUtil from 'oae-rest/lib/util'; +import * as TestsUtil from 'oae-tests'; + +import * as ContentTestUtil from 'oae-content/lib/test/util'; +import * as Etherpad from 'oae-content/lib/internal/etherpad'; describe('Content Activity', () => { // Rest contexts that can be used for performing REST requests @@ -110,31 +111,16 @@ describe('Content Activity', () => { assert.ok(!err); const groupBmembers = {}; groupBmembers[groupMemberB.user.id] = 'member'; - RestAPI.Group.setGroupMembers( - simong.restContext, - groupB.group.id, - groupBmembers, - err => { - assert.ok(!err); + RestAPI.Group.setGroupMembers(simong.restContext, groupB.group.id, groupBmembers, err => { + assert.ok(!err); - // Nico follows simong - RestAPI.Following.follow(nico.restContext, simong.user.id, err => { - assert.ok(!err); + // Nico follows simong + RestAPI.Following.follow(nico.restContext, simong.user.id, err => { + assert.ok(!err); - return callback( - simong, - nico, - bert, - stuart, - stephen, - groupMemberA, - groupMemberB, - groupA, - groupB - ); - }); - } - ); + return callback(simong, nico, bert, stuart, stephen, groupMemberA, groupMemberB, groupA, groupB); + }); + }); }); }); } @@ -142,14 +128,14 @@ describe('Content Activity', () => { }; /*! - * Get the activity from the stream with the given criteria - * - * @param {ActivityStream} activityStream The stream to search - * @param {String} activityType The type of activity to find - * @param {String} entityType The type of entity to apply the criteria (one of actor, object or target) - * @param {String} entityOaeId The oae:id of the entity to search - * @return {Activity} An activity from the stream that matches the provided criteria - */ + * Get the activity from the stream with the given criteria + * + * @param {ActivityStream} activityStream The stream to search + * @param {String} activityType The type of activity to find + * @param {String} entityType The type of entity to apply the criteria (one of actor, object or target) + * @param {String} entityOaeId The oae:id of the entity to search + * @return {Activity} An activity from the stream that matches the provided criteria + */ const _getActivity = function(activityStream, activityType, entityType, entityOaeId) { if (!activityStream) { return null; @@ -165,12 +151,12 @@ describe('Content Activity', () => { }; /*! - * Get the email from the email list with the given to address - * - * @param {Object[]} emails The emails to search - * @param {String} to The email address to which the email should be sent - * @return {Object} The first email from the email list that matches the to address - */ + * Get the email from the email list with the given to address + * + * @param {Object[]} emails The emails to search + * @param {String} to The email address to which the email should be sent + * @return {Object} The first email from the email list that matches the to address + */ const _getEmail = function(emails, to) { return _.find(emails, email => { return email.to[0].address === to; @@ -196,225 +182,135 @@ describe('Content Activity', () => { * Test that verifies a content resource routes activities to its members when created, updated and shared */ it('verify routing to content members', callback => { - TestsUtil.generateTestUsers( - camAdminRestContext, - 3, - (err, users, jack, jane, managerGroupMember) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 3, (err, users, jack, jane, managerGroupMember) => { + assert.ok(!err); - // Create the group that will be a viewer of the content - RestAPI.Group.createGroup( - camAdminRestContext, - 'Viewer Group displayName', - 'Viewer Group Description', - 'public', - 'no', - [], - [], - (err, viewerGroup) => { - assert.ok(!err); + // Create the group that will be a viewer of the content + RestAPI.Group.createGroup( + camAdminRestContext, + 'Viewer Group displayName', + 'Viewer Group Description', + 'public', + 'no', + [], + [], + (err, viewerGroup) => { + assert.ok(!err); - // Create a group that will be a manager of the content - RestAPI.Group.createGroup( - camAdminRestContext, - 'Manager Group displayName', - 'Manager Group Description', - 'public', - 'no', - [], - [], - (err, managerGroup) => { + // Create a group that will be a manager of the content + RestAPI.Group.createGroup( + camAdminRestContext, + 'Manager Group displayName', + 'Manager Group Description', + 'public', + 'no', + [], + [], + (err, managerGroup) => { + assert.ok(!err); + + // ManagerGroupMember should be a member of the manager group to verify indirect group member routing + const membership = {}; + membership[managerGroupMember.user.id] = 'manager'; + RestAPI.Group.setGroupMembers(camAdminRestContext, managerGroup.id, membership, err => { assert.ok(!err); - // ManagerGroupMember should be a member of the manager group to verify indirect group member routing - const membership = {}; - membership[managerGroupMember.user.id] = 'manager'; - RestAPI.Group.setGroupMembers( - camAdminRestContext, - managerGroup.id, - membership, - err => { + // Create a content item with manager group and viewer group as members. + RestAPI.Content.createLink( + jack.restContext, + 'Google', + 'Google', + 'public', + 'http://www.google.ca', + [managerGroup.id], + [viewerGroup.id], + [], + (err, link) => { assert.ok(!err); - // Create a content item with manager group and viewer group as members. - RestAPI.Content.createLink( - jack.restContext, - 'Google', - 'Google', - 'public', - 'http://www.google.ca', - [managerGroup.id], - [viewerGroup.id], - [], - (err, link) => { - assert.ok(!err); - - // Share the content item with jane - RestAPI.Content.shareContent( - jack.restContext, - link.id, - [jane.user.id], - err => { - assert.ok(!err); + // Share the content item with jane + RestAPI.Content.shareContent(jack.restContext, link.id, [jane.user.id], err => { + assert.ok(!err); - // Update the content item - RestAPI.Content.updateContent( - jack.restContext, - link.id, - { description: 'Super awesome link' }, - err => { - assert.ok(!err); + // Update the content item + RestAPI.Content.updateContent( + jack.restContext, + link.id, + { description: 'Super awesome link' }, + err => { + assert.ok(!err); - // Verify Jack got the create, share and update as he was the actor for all of them - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - jack.user.id, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok( - _getActivity( - activityStream, - 'content-create', - 'object', - link.id - ) - ); - assert.ok( - _getActivity( - activityStream, - 'content-share', - 'target', - jane.user.id - ) - ); - assert.ok( - _getActivity( - activityStream, - 'content-update', - 'object', - link.id - ) - ); + // Verify Jack got the create, share and update as he was the actor for all of them + ActivityTestsUtil.collectAndGetActivityStream( + jack.restContext, + jack.user.id, + null, + (err, activityStream) => { + assert.ok(!err); + assert.ok(_getActivity(activityStream, 'content-create', 'object', link.id)); + assert.ok(_getActivity(activityStream, 'content-share', 'target', jane.user.id)); + assert.ok(_getActivity(activityStream, 'content-update', 'object', link.id)); - // Verify the manager group received the create, share and update as they are a content member - ActivityTestsUtil.collectAndGetActivityStream( - camAdminRestContext, - managerGroup.id, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok( - _getActivity( - activityStream, - 'content-create', - 'object', - link.id - ) - ); - assert.ok( - _getActivity( - activityStream, - 'content-share', - 'target', - jane.user.id - ) - ); - assert.ok( - _getActivity( - activityStream, - 'content-update', - 'object', - link.id - ) - ); + // Verify the manager group received the create, share and update as they are a content member + ActivityTestsUtil.collectAndGetActivityStream( + camAdminRestContext, + managerGroup.id, + null, + (err, activityStream) => { + assert.ok(!err); + assert.ok(_getActivity(activityStream, 'content-create', 'object', link.id)); + assert.ok(_getActivity(activityStream, 'content-share', 'target', jane.user.id)); + assert.ok(_getActivity(activityStream, 'content-update', 'object', link.id)); - // Verify the viewer group received only the create and update. only managers care about the sharing of the "object" - ActivityTestsUtil.collectAndGetActivityStream( - camAdminRestContext, - viewerGroup.id, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok( - _getActivity( - activityStream, - 'content-create', - 'object', - link.id - ) - ); - assert.ok( - !_getActivity( - activityStream, - 'content-share', - 'target', - jane.user.id - ) - ); - assert.ok( - _getActivity( - activityStream, - 'content-update', - 'object', - link.id - ) - ); + // Verify the viewer group received only the create and update. only managers care about the sharing of the "object" + ActivityTestsUtil.collectAndGetActivityStream( + camAdminRestContext, + viewerGroup.id, + null, + (err, activityStream) => { + assert.ok(!err); + assert.ok(_getActivity(activityStream, 'content-create', 'object', link.id)); + assert.ok( + !_getActivity(activityStream, 'content-share', 'target', jane.user.id) + ); + assert.ok(_getActivity(activityStream, 'content-update', 'object', link.id)); - // Verify the manager group *member* got the same activities as the manager group, as they are a member - ActivityTestsUtil.collectAndGetActivityStream( - camAdminRestContext, - managerGroupMember.user.id, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok( - _getActivity( - activityStream, - 'content-create', - 'object', - link.id - ) - ); - assert.ok( - _getActivity( - activityStream, - 'content-share', - 'target', - jane.user.id - ) - ); - assert.ok( - _getActivity( - activityStream, - 'content-update', - 'object', - link.id - ) - ); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + // Verify the manager group *member* got the same activities as the manager group, as they are a member + ActivityTestsUtil.collectAndGetActivityStream( + camAdminRestContext, + managerGroupMember.user.id, + null, + (err, activityStream) => { + assert.ok(!err); + assert.ok( + _getActivity(activityStream, 'content-create', 'object', link.id) + ); + assert.ok( + _getActivity(activityStream, 'content-share', 'target', jane.user.id) + ); + assert.ok( + _getActivity(activityStream, 'content-update', 'object', link.id) + ); + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + }); } ); - } - ); - } - ); - } - ); + }); + } + ); + } + ); + }); }); /** @@ -458,10 +354,7 @@ describe('Content Activity', () => { assert.strictEqual(object['oae:resourceSubType'], 'link'); assert.strictEqual( object['oae:profilePath'], - '/content/' + - link.tenant.alias + - '/' + - AuthzUtil.getResourceFromId(link.id).resourceId + '/content/' + link.tenant.alias + '/' + AuthzUtil.getResourceFromId(link.id).resourceId ); assert.strictEqual(object.displayName, 'Google'); return callback(); @@ -521,10 +414,7 @@ describe('Content Activity', () => { // Should have exactly 1 activity, 2 aggregated comments assert.strictEqual(activityStream.items.length, 1); - assert.strictEqual( - activityStream.items[0].object['oae:collection'].length, - 2 - ); + assert.strictEqual(activityStream.items[0].object['oae:collection'].length, 2); callback(); } ); @@ -568,51 +458,38 @@ describe('Content Activity', () => { assert.ok(!err); // Force a collection before the content item goes private - ActivityTestsUtil.collectAndGetActivityStream( - mrvisser.restContext, - mrvisser.user.id, - null, - err => { + ActivityTestsUtil.collectAndGetActivityStream(mrvisser.restContext, mrvisser.user.id, null, err => { + assert.ok(!err); + + // Simong has had enough of mrvisser's tom-foolery and makes the content item private + RestAPI.Content.updateContent(simong.restContext, link.id, { visibility: 'private' }, err => { assert.ok(!err); - // Simong has had enough of mrvisser's tom-foolery and makes the content item private - RestAPI.Content.updateContent( - simong.restContext, + // Bert retorts! + RestAPI.Content.createComment( + bert.restContext, link.id, - { visibility: 'private' }, - err => { + "You're wrong and you smell bad!", + null, + (err, bertComment) => { assert.ok(!err); - // Bert retorts! - RestAPI.Content.createComment( - bert.restContext, - link.id, - "You're wrong and you smell bad!", + // Mrvisser should only have the activity for the comment he made, not Bert's + ActivityTestsUtil.collectAndGetActivityStream( + mrvisser.restContext, + mrvisser.user.id, null, - (err, bertComment) => { + (err, activityStream) => { assert.ok(!err); - - // Mrvisser should only have the activity for the comment he made, not Bert's - ActivityTestsUtil.collectAndGetActivityStream( - mrvisser.restContext, - mrvisser.user.id, - null, - (err, activityStream) => { - assert.ok(!err); - assert.strictEqual(activityStream.items.length, 1); - assert.strictEqual( - activityStream.items[0].object['oae:id'], - mrvisserComment.id - ); - callback(); - } - ); + assert.strictEqual(activityStream.items.length, 1); + assert.strictEqual(activityStream.items[0].object['oae:id'], mrvisserComment.id); + callback(); } ); } ); - } - ); + }); + }); } ); } @@ -639,62 +516,51 @@ describe('Content Activity', () => { // Give one of the users a profile picture const cropArea = { x: 0, y: 0, width: 250, height: 250 }; - RestAPI.User.uploadPicture( - mrvisser.restContext, - mrvisser.user.id, - getPictureStream, - cropArea, - err => { - assert.ok(!err); + RestAPI.User.uploadPicture(mrvisser.restContext, mrvisser.user.id, getPictureStream, cropArea, err => { + assert.ok(!err); - // Create a content item to be commented on - RestAPI.Content.createLink( - simong.restContext, - 'Google', - 'Google', - 'public', - 'http://www.google.ca', - [], - [], - [], - (err, link) => { - assert.ok(!err); + // Create a content item to be commented on + RestAPI.Content.createLink( + simong.restContext, + 'Google', + 'Google', + 'public', + 'http://www.google.ca', + [], + [], + [], + (err, link) => { + assert.ok(!err); - // Mrvisser is not a member, but he will comment on it - RestAPI.Content.createComment( - mrvisser.restContext, - link.id, - 'This link clearly goes to Google.', - null, - (err, mrvisserComment) => { - assert.ok(!err); + // Mrvisser is not a member, but he will comment on it + RestAPI.Content.createComment( + mrvisser.restContext, + link.id, + 'This link clearly goes to Google.', + null, + (err, mrvisserComment) => { + assert.ok(!err); - // Mrvisser should have a notification and an activity about this because he was a recent commenter - ActivityTestsUtil.collectAndGetActivityStream( - mrvisser.restContext, - mrvisser.user.id, - null, - (err, activityStream) => { - assert.ok(!err); - assert.strictEqual(activityStream.items.length, 1); - assert.strictEqual( - activityStream.items[0].object['oae:id'], - mrvisserComment.id - ); - assert.ok(activityStream.items[0].object.author.image); - assert.ok(activityStream.items[0].object.author.image.url); - assert.ok( - activityStream.items[0].object.author.image.url.indexOf('expired') === -1 - ); - callback(); - } - ); - } - ); - } - ); - } - ); + // Mrvisser should have a notification and an activity about this because he was a recent commenter + ActivityTestsUtil.collectAndGetActivityStream( + mrvisser.restContext, + mrvisser.user.id, + null, + (err, activityStream) => { + assert.ok(!err); + assert.strictEqual(activityStream.items.length, 1); + assert.strictEqual(activityStream.items[0].object['oae:id'], mrvisserComment.id); + assert.ok(activityStream.items[0].object.author.image); + assert.ok(activityStream.items[0].object.author.image.url); + assert.ok(activityStream.items[0].object.author.image.url.indexOf('expired') === -1); + callback(); + } + ); + } + ); + } + ); + }); }); }); @@ -731,119 +597,100 @@ describe('Content Activity', () => { assert.ok(!err); ContentTestUtil.publishCollabDoc(contentObj.id, branden.user.id, () => { // Route and aggregate the activity into branden's activity stream - ActivityTestsUtil.collectAndGetActivityStream( - branden.restContext, - branden.user.id, - null, - err => { - assert.ok(!err); - - // Query branden's activity stream to get the item that was persisted - const activityStreamId = util.format('%s#activity', branden.user.id); - Cassandra.runQuery( - 'SELECT * FROM "ActivityStreams" WHERE "activityStreamId" = ?', - [activityStreamId], - (err, rows) => { - assert.ok(!err); - assert.strictEqual(rows.length, 2); - - // Ensure we get the revision activity, and that there is no latest - // revision content - const hash = Cassandra.rowToHash(rows[1]); - const activity = JSON.parse(hash.activity); - assert.strictEqual(activity['oae:activityType'], 'content-revision'); - assert.strictEqual(activity.actor.id, branden.user.id); - assert.strictEqual(activity.object['oae:id'], contentObj.id); - assert.ok(!activity.object.content.latestRevision); - - // Comment on the activity, ensuring there is no latest revision content - RestAPI.Content.createComment( - branden.restContext, - contentObj.id, - 'Comment A', - null, - (err, commentA) => { - assert.ok(!err); - ActivityTestsUtil.collectAndGetActivityStream( - branden.restContext, - branden.user.id, - null, - err => { - Cassandra.runQuery( - 'SELECT * FROM "ActivityStreams" WHERE "activityStreamId" = ?', - [activityStreamId], - (err, rows) => { - assert.ok(!err); - assert.strictEqual(rows.length, 3); - - // Ensure we get the comment activity, and that there is no latest - // revision content - const hash = Cassandra.rowToHash(rows[2]); - const activity = JSON.parse(hash.activity); - assert.strictEqual( - activity['oae:activityType'], - 'content-comment' - ); - assert.strictEqual(activity.actor.id, branden.user.id); - assert.strictEqual(activity.target['oae:id'], contentObj.id); - assert.ok(!activity.target.content.latestRevision); - - // Share the activity, ensuring there is no latest revision content - RestAPI.Content.shareContent( - branden.restContext, - contentObj.id, - [nico.user.id], - err => { - assert.ok(!err); - ActivityTestsUtil.collectAndGetActivityStream( - branden.restContext, - branden.user.id, - null, - err => { - Cassandra.runQuery( - 'SELECT * FROM "ActivityStreams" WHERE "activityStreamId" = ?', - [activityStreamId], - (err, rows) => { - assert.ok(!err); - assert.strictEqual(rows.length, 4); - - // Ensure we get the share activity, and that there is no latest - // revision content - const hash = Cassandra.rowToHash(rows[3]); - const activity = JSON.parse(hash.activity); - assert.strictEqual( - activity['oae:activityType'], - 'content-share' - ); - assert.strictEqual( - activity.actor.id, - branden.user.id - ); - assert.strictEqual( - activity.object['oae:id'], - contentObj.id - ); - assert.ok( - !activity.object.content.latestRevision - ); + ActivityTestsUtil.collectAndGetActivityStream(branden.restContext, branden.user.id, null, err => { + assert.ok(!err); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + // Query branden's activity stream to get the item that was persisted + const activityStreamId = util.format('%s#activity', branden.user.id); + Cassandra.runQuery( + 'SELECT * FROM "ActivityStreams" WHERE "activityStreamId" = ?', + [activityStreamId], + (err, rows) => { + assert.ok(!err); + assert.strictEqual(rows.length, 2); + + // Ensure we get the revision activity, and that there is no latest + // revision content + const hash = Cassandra.rowToHash(rows[1]); + const activity = JSON.parse(hash.activity); + assert.strictEqual(activity['oae:activityType'], 'content-revision'); + assert.strictEqual(activity.actor.id, branden.user.id); + assert.strictEqual(activity.object['oae:id'], contentObj.id); + assert.ok(!activity.object.content.latestRevision); + + // Comment on the activity, ensuring there is no latest revision content + RestAPI.Content.createComment( + branden.restContext, + contentObj.id, + 'Comment A', + null, + (err, commentA) => { + assert.ok(!err); + ActivityTestsUtil.collectAndGetActivityStream( + branden.restContext, + branden.user.id, + null, + err => { + Cassandra.runQuery( + 'SELECT * FROM "ActivityStreams" WHERE "activityStreamId" = ?', + [activityStreamId], + (err, rows) => { + assert.ok(!err); + assert.strictEqual(rows.length, 3); + + // Ensure we get the comment activity, and that there is no latest + // revision content + const hash = Cassandra.rowToHash(rows[2]); + const activity = JSON.parse(hash.activity); + assert.strictEqual(activity['oae:activityType'], 'content-comment'); + assert.strictEqual(activity.actor.id, branden.user.id); + assert.strictEqual(activity.target['oae:id'], contentObj.id); + assert.ok(!activity.target.content.latestRevision); + + // Share the activity, ensuring there is no latest revision content + RestAPI.Content.shareContent( + branden.restContext, + contentObj.id, + [nico.user.id], + err => { + assert.ok(!err); + ActivityTestsUtil.collectAndGetActivityStream( + branden.restContext, + branden.user.id, + null, + err => { + Cassandra.runQuery( + 'SELECT * FROM "ActivityStreams" WHERE "activityStreamId" = ?', + [activityStreamId], + (err, rows) => { + assert.ok(!err); + assert.strictEqual(rows.length, 4); + + // Ensure we get the share activity, and that there is no latest + // revision content + const hash = Cassandra.rowToHash(rows[3]); + const activity = JSON.parse(hash.activity); + assert.strictEqual(activity['oae:activityType'], 'content-share'); + assert.strictEqual(activity.actor.id, branden.user.id); + assert.strictEqual(activity.object['oae:id'], contentObj.id); + assert.ok(!activity.object.content.latestRevision); + + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); }); }); }); @@ -897,11 +744,7 @@ describe('Content Activity', () => { assert.strictEqual(simonEmail.to[0].address, simon.user.email); assert.strictEqual( simonEmail.subject, - util.format( - '%s edited the document "%s"', - branden.user.displayName, - contentObj.displayName - ) + util.format('%s edited the document "%s"', branden.user.displayName, contentObj.displayName) ); assert.notStrictEqual(simonEmail.html.indexOf(contentObj.profilePath), -1); @@ -910,11 +753,7 @@ describe('Content Activity', () => { assert.strictEqual(nicoEmail.to[0].address, nico.user.email); assert.strictEqual( nicoEmail.subject, - util.format( - '%s edited the document "%s"', - branden.user.displayName, - contentObj.displayName - ) + util.format('%s edited the document "%s"', branden.user.displayName, contentObj.displayName) ); assert.notStrictEqual(nicoEmail.html.indexOf(contentObj.profilePath), -1); @@ -923,65 +762,32 @@ describe('Content Activity', () => { assert.ok(!brandenEmail); // There should be a notification in Simon's stream as he is a manager - ActivityTestsUtil.collectAndGetNotificationStream( - simon.restContext, - null, - (err, data) => { + ActivityTestsUtil.collectAndGetNotificationStream(simon.restContext, null, (err, data) => { + assert.ok(!err); + const notificationSimon = _getActivity(data, 'content-revision', 'object', contentObj.id); + assert.ok(notificationSimon); + assert.strictEqual(notificationSimon['oae:activityType'], 'content-revision'); + assert.strictEqual(notificationSimon.actor['oae:id'], branden.user.id); + assert.strictEqual(notificationSimon.object['oae:id'], contentObj.id); + + // There should be a notification in Nico's stream as he is a member + ActivityTestsUtil.collectAndGetNotificationStream(nico.restContext, null, (err, data) => { assert.ok(!err); - const notificationSimon = _getActivity( - data, - 'content-revision', - 'object', - contentObj.id - ); - assert.ok(notificationSimon); - assert.strictEqual( - notificationSimon['oae:activityType'], - 'content-revision' - ); - assert.strictEqual(notificationSimon.actor['oae:id'], branden.user.id); - assert.strictEqual(notificationSimon.object['oae:id'], contentObj.id); - - // There should be a notification in Nico's stream as he is a member - ActivityTestsUtil.collectAndGetNotificationStream( - nico.restContext, - null, - (err, data) => { - assert.ok(!err); - const notificationNico = _getActivity( - data, - 'content-revision', - 'object', - contentObj.id - ); - assert.ok(notificationNico); - assert.strictEqual( - notificationNico['oae:activityType'], - 'content-revision' - ); - assert.strictEqual(notificationNico.actor['oae:id'], branden.user.id); - assert.strictEqual(notificationNico.object['oae:id'], contentObj.id); - - // There should be no notification in Branden's stream as he published the change - ActivityTestsUtil.collectAndGetNotificationStream( - branden.restContext, - null, - (err, data) => { - assert.ok(!err); - const notificatioBranden = _getActivity( - data, - 'content-revision', - 'object', - contentObj.id - ); - assert.ok(!notificatioBranden); - return callback(); - } - ); - } - ); - } - ); + const notificationNico = _getActivity(data, 'content-revision', 'object', contentObj.id); + assert.ok(notificationNico); + assert.strictEqual(notificationNico['oae:activityType'], 'content-revision'); + assert.strictEqual(notificationNico.actor['oae:id'], branden.user.id); + assert.strictEqual(notificationNico.object['oae:id'], contentObj.id); + + // There should be no notification in Branden's stream as he published the change + ActivityTestsUtil.collectAndGetNotificationStream(branden.restContext, null, (err, data) => { + assert.ok(!err); + const notificatioBranden = _getActivity(data, 'content-revision', 'object', contentObj.id); + assert.ok(!notificatioBranden); + return callback(); + }); + }); + }); }); }); }); @@ -996,74 +802,58 @@ describe('Content Activity', () => { * Test that verifies that an activity is generated regardless of whether there was an update to a is collaborative document since the last revision */ it('verify an activity is generated regardless of whether there was an update to a is collaborative document since the last revision', callback => { - ContentTestUtil.createCollabDoc( - camAdminRestContext, - 2, - 2, - (contentObj, users, simon, nico) => { - // Set some text in the pad - const etherpadClient = Etherpad.getClient(contentObj.id); - const args = { - padID: contentObj.etherpadPadId, - text: 'Collaborative editing by Simon and Nico! Oooooh!' - }; - etherpadClient.setText(args, err => { - assert.ok(!err); + ContentTestUtil.createCollabDoc(camAdminRestContext, 2, 2, (contentObj, users, simon, nico) => { + // Set some text in the pad + const etherpadClient = Etherpad.getClient(contentObj.id); + const args = { + padID: contentObj.etherpadPadId, + text: 'Collaborative editing by Simon and Nico! Oooooh!' + }; + etherpadClient.setText(args, err => { + assert.ok(!err); - // Lets assume that both users are editting the document. First, Simon leaves - ContentTestUtil.publishCollabDoc(contentObj.id, simon.user.id, () => { - // Now, Nico leaves WITHOUT making any *extra* edits to the document. But because - // he made edits earlier, we should still generate an activity - ContentTestUtil.publishCollabDoc(contentObj.id, nico.user.id, () => { - // Assert that there is an aggregated activity for an updated document that holds - // both Simon and Nico as the actors - ActivityTestsUtil.collectAndGetActivityStream( - nico.restContext, - nico.user.id, - null, - (err, data) => { - assert.ok(!err); - ActivityTestsUtil.assertActivity( - data.items[0], - 'content-revision', - 'update', - [simon.user.id, nico.user.id], - contentObj.id - ); + // Lets assume that both users are editting the document. First, Simon leaves + ContentTestUtil.publishCollabDoc(contentObj.id, simon.user.id, () => { + // Now, Nico leaves WITHOUT making any *extra* edits to the document. But because + // he made edits earlier, we should still generate an activity + ContentTestUtil.publishCollabDoc(contentObj.id, nico.user.id, () => { + // Assert that there is an aggregated activity for an updated document that holds + // both Simon and Nico as the actors + ActivityTestsUtil.collectAndGetActivityStream(nico.restContext, nico.user.id, null, (err, data) => { + assert.ok(!err); + ActivityTestsUtil.assertActivity( + data.items[0], + 'content-revision', + 'update', + [simon.user.id, nico.user.id], + contentObj.id + ); - // Sanity-check there are 2 revisions, the initial empty one + the one "published" revision - RestAPI.Content.getRevisions( - nico.restContext, - contentObj.id, - null, - null, - (err, data) => { - assert.ok(!err); - assert.strictEqual(data.results.length, 2); + // Sanity-check there are 2 revisions, the initial empty one + the one "published" revision + RestAPI.Content.getRevisions(nico.restContext, contentObj.id, null, null, (err, data) => { + assert.ok(!err); + assert.strictEqual(data.results.length, 2); - // Get the latest revision - RestAPI.Content.getRevision( - nico.restContext, - contentObj.id, - data.results[0].revisionId, - (err, revision) => { - assert.ok(!err); - assert.ok(revision); + // Get the latest revision + RestAPI.Content.getRevision( + nico.restContext, + contentObj.id, + data.results[0].revisionId, + (err, revision) => { + assert.ok(!err); + assert.ok(revision); - // Assert the text is in the latest revision - assert.ok(revision.etherpadHtml.indexOf(args.text) > -1); - return callback(); - } - ); - } - ); - } - ); + // Assert the text is in the latest revision + assert.ok(revision.etherpadHtml.indexOf(args.text) > -1); + return callback(); + } + ); + }); }); }); }); - } - ); + }); + }); }); /** @@ -1109,116 +899,65 @@ describe('Content Activity', () => { assert.ok(!err); // Verify the activity streams. All users should have received an activity - ActivityTestsUtil.collectAndGetActivityStream( - brandenCtx, - null, - null, - (err, data) => { + ActivityTestsUtil.collectAndGetActivityStream(brandenCtx, null, null, (err, data) => { + assert.ok(!err); + const activity = _getActivity(data, 'content-restored-revision', 'object', content.id); + assert.ok(activity); + assert.strictEqual(activity['oae:activityType'], 'content-restored-revision'); + assert.strictEqual(activity.actor['oae:id'], brandenId); + assert.strictEqual(activity.object['oae:id'], content.id); + + ActivityTestsUtil.collectAndGetActivityStream(simonCtx, null, null, (err, data) => { assert.ok(!err); - const activity = _getActivity( - data, - 'content-restored-revision', - 'object', - content.id - ); + const activity = _getActivity(data, 'content-restored-revision', 'object', content.id); assert.ok(activity); - assert.strictEqual( - activity['oae:activityType'], - 'content-restored-revision' - ); + assert.strictEqual(activity['oae:activityType'], 'content-restored-revision'); assert.strictEqual(activity.actor['oae:id'], brandenId); assert.strictEqual(activity.object['oae:id'], content.id); - ActivityTestsUtil.collectAndGetActivityStream( - simonCtx, - null, - null, - (err, data) => { + ActivityTestsUtil.collectAndGetActivityStream(nicoCtx, null, null, (err, data) => { + assert.ok(!err); + const notificationSimon = _getActivity( + data, + 'content-restored-revision', + 'object', + content.id + ); + assert.ok(notificationSimon); + assert.strictEqual(notificationSimon['oae:activityType'], 'content-restored-revision'); + assert.strictEqual(notificationSimon.actor['oae:id'], brandenId); + assert.strictEqual(notificationSimon.object['oae:id'], content.id); + + // There should also be a notification in Simon's stream as he is a manager + ActivityTestsUtil.collectAndGetNotificationStream(simonCtx, null, (err, data) => { assert.ok(!err); - const activity = _getActivity( + const notificationSimon = _getActivity( data, 'content-restored-revision', 'object', content.id ); - assert.ok(activity); - assert.strictEqual( - activity['oae:activityType'], - 'content-restored-revision' - ); - assert.strictEqual(activity.actor['oae:id'], brandenId); - assert.strictEqual(activity.object['oae:id'], content.id); - - ActivityTestsUtil.collectAndGetActivityStream( - nicoCtx, - null, - null, - (err, data) => { - assert.ok(!err); - const notificationSimon = _getActivity( - data, - 'content-restored-revision', - 'object', - content.id - ); - assert.ok(notificationSimon); - assert.strictEqual( - notificationSimon['oae:activityType'], - 'content-restored-revision' - ); - assert.strictEqual(notificationSimon.actor['oae:id'], brandenId); - assert.strictEqual(notificationSimon.object['oae:id'], content.id); - - // There should also be a notification in Simon's stream as he is a manager - ActivityTestsUtil.collectAndGetNotificationStream( - simonCtx, - null, - (err, data) => { - assert.ok(!err); - const notificationSimon = _getActivity( - data, - 'content-restored-revision', - 'object', - content.id - ); - assert.ok(notificationSimon); - assert.strictEqual( - notificationSimon['oae:activityType'], - 'content-restored-revision' - ); - assert.strictEqual( - notificationSimon.actor['oae:id'], - brandenId - ); - assert.strictEqual( - notificationSimon.object['oae:id'], - content.id - ); + assert.ok(notificationSimon); + assert.strictEqual(notificationSimon['oae:activityType'], 'content-restored-revision'); + assert.strictEqual(notificationSimon.actor['oae:id'], brandenId); + assert.strictEqual(notificationSimon.object['oae:id'], content.id); - // There should be no notification in Nico's stream as he is not a manager - ActivityTestsUtil.collectAndGetNotificationStream( - nicoCtx, - null, - (err, data) => { - assert.ok(!err); - const notificationNico = _getActivity( - data, - 'content-restored-revision', - 'object', - content.id - ); - assert.ok(!notificationNico); - callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + // There should be no notification in Nico's stream as he is not a manager + ActivityTestsUtil.collectAndGetNotificationStream(nicoCtx, null, (err, data) => { + assert.ok(!err); + const notificationNico = _getActivity( + data, + 'content-restored-revision', + 'object', + content.id + ); + assert.ok(!notificationNico); + callback(); + }); + }); + }); + }); + }); } ); } @@ -1232,268 +971,166 @@ describe('Content Activity', () => { * Test that verifies that content-share or content-add-to-library activities are routed to the content's activity stream */ it('verify content-share or content-add-to-library activities are not routed to the content activity stream', callback => { - TestsUtil.generateTestUsers( - camAdminRestContext, - 4, - (err, users, simon, nico, bert, stuart) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 4, (err, users, simon, nico, bert, stuart) => { + assert.ok(!err); - RestAPI.Content.createLink( - simon.restContext, - 'Google', - 'Google', - 'public', - 'http://www.google.ca', - [], - [], - [], - (err, contentObj) => { + RestAPI.Content.createLink( + simon.restContext, + 'Google', + 'Google', + 'public', + 'http://www.google.ca', + [], + [], + [], + (err, contentObj) => { + assert.ok(!err); + + // Make Nico a private user and add the file into his library, no activities should be sent + RestAPI.User.updateUser(nico.restContext, nico.user.id, { visibility: 'private' }, err => { assert.ok(!err); + RestAPI.Content.shareContent(nico.restContext, contentObj.id, [nico.user.id], err => { + assert.ok(!err); - // Make Nico a private user and add the file into his library, no activities should be sent - RestAPI.User.updateUser( - nico.restContext, - nico.user.id, - { visibility: 'private' }, - err => { + // Route and deliver activities + ActivityTestsUtil.collectAndGetActivityStream(nico.restContext, null, null, err => { assert.ok(!err); - RestAPI.Content.shareContent( - nico.restContext, - contentObj.id, - [nico.user.id], - err => { - assert.ok(!err); - - // Route and deliver activities - ActivityTestsUtil.collectAndGetActivityStream( - nico.restContext, - null, - null, - err => { - assert.ok(!err); - // Assert they didn't end up in the content activity stream - ActivityDAO.getActivities( - contentObj.id + '#activity', - null, - 25, - (err, activities) => { - assert.ok(!err); - // Assert that we didn't add the `content-add-to-library` activity by asserting the latest activity in the stream is `content-create` - assert.strictEqual( - activities[0]['oae:activityType'], - 'content-create' - ); + // Assert they didn't end up in the content activity stream + ActivityDAO.getActivities(contentObj.id + '#activity', null, 25, (err, activities) => { + assert.ok(!err); + // Assert that we didn't add the `content-add-to-library` activity by asserting the latest activity in the stream is `content-create` + assert.strictEqual(activities[0]['oae:activityType'], 'content-create'); - // Try it with a public user - RestAPI.Content.shareContent( - bert.restContext, - contentObj.id, - [bert.user.id], - err => { - assert.ok(!err); + // Try it with a public user + RestAPI.Content.shareContent(bert.restContext, contentObj.id, [bert.user.id], err => { + assert.ok(!err); - // Route and deliver activities - ActivityTestsUtil.collectAndGetActivityStream( - bert.restContext, - null, - null, - err => { - assert.ok(!err); + // Route and deliver activities + ActivityTestsUtil.collectAndGetActivityStream(bert.restContext, null, null, err => { + assert.ok(!err); - // Assert they didn't end up in the content activity stream - ActivityDAO.getActivities( - contentObj.id + '#activity', - null, - 25, - (err, activities) => { - assert.ok(!err); - // Assert that we didn't add the `content-add-to-library` activity by asserting the latest activity in the stream is `content-create` - assert.strictEqual( - activities[0]['oae:activityType'], - 'content-create' - ); + // Assert they didn't end up in the content activity stream + ActivityDAO.getActivities(contentObj.id + '#activity', null, 25, (err, activities) => { + assert.ok(!err); + // Assert that we didn't add the `content-add-to-library` activity by asserting the latest activity in the stream is `content-create` + assert.strictEqual(activities[0]['oae:activityType'], 'content-create'); - // Assert that content-share activities do not end up on the activity stream - RestAPI.Content.shareContent( - bert.restContext, - contentObj.id, - [stuart.user.id], - err => { - assert.ok(!err); + // Assert that content-share activities do not end up on the activity stream + RestAPI.Content.shareContent(bert.restContext, contentObj.id, [stuart.user.id], err => { + assert.ok(!err); - // Route and deliver activities - ActivityTestsUtil.collectAndGetActivityStream( - stuart.restContext, - null, - null, - err => { - assert.ok(!err); + // Route and deliver activities + ActivityTestsUtil.collectAndGetActivityStream(stuart.restContext, null, null, err => { + assert.ok(!err); - // Assert they didn't end up in the content activity stream - ActivityDAO.getActivities( - contentObj.id + '#activity', - null, - 25, - (err, activities) => { - assert.ok(!err); - // Assert that we didn't add the `content-share` activity by asserting the latest activity in the stream is `content-create` - assert.strictEqual( - activities[0]['oae:activityType'], - 'content-create' - ); + // Assert they didn't end up in the content activity stream + ActivityDAO.getActivities(contentObj.id + '#activity', null, 25, (err, activities) => { + assert.ok(!err); + // Assert that we didn't add the `content-share` activity by asserting the latest activity in the stream is `content-create` + assert.strictEqual(activities[0]['oae:activityType'], 'content-create'); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + return callback(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + } + ); + }); }); /** * Test that verifies that a comment activity is routed to the managers and recent contributers their notification stream of a private content item */ it('verify comment activity is routed to the managers and recent contributers notification stream of a private content item', callback => { - TestsUtil.generateTestUsers( - camAdminRestContext, - 4, - (err, users, simon, nico, bert, stuart) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 4, (err, users, simon, nico, bert, stuart) => { + assert.ok(!err); - RestAPI.Content.createLink( - simon.restContext, - 'Google', - 'Google', - 'public', - 'http://www.google.ca', - [nico.user.id], - [bert.user.id, stuart.user.id], - [], - (err, link) => { + RestAPI.Content.createLink( + simon.restContext, + 'Google', + 'Google', + 'public', + 'http://www.google.ca', + [nico.user.id], + [bert.user.id, stuart.user.id], + [], + (err, link) => { + assert.ok(!err); + + RestAPI.Content.createComment(bert.restContext, link.id, 'Comment A', null, (err, commentA) => { assert.ok(!err); - RestAPI.Content.createComment( - bert.restContext, - link.id, - 'Comment A', - null, - (err, commentA) => { + // Assert that the managers got it + ActivityTestsUtil.collectAndGetNotificationStream(simon.restContext, null, (err, activityStream) => { + assert.ok(!err); + assert.ok( + _.find(activityStream.items, activity => { + return activity['oae:activityType'] === 'content-comment'; + }) + ); + + ActivityTestsUtil.collectAndGetNotificationStream(nico.restContext, null, (err, activityStream) => { assert.ok(!err); + assert.ok( + _.find(activityStream.items, activity => { + return activity['oae:activityType'] === 'content-comment'; + }) + ); - // Assert that the managers got it - ActivityTestsUtil.collectAndGetNotificationStream( - simon.restContext, - null, - (err, activityStream) => { + // Create another comment and assert that both the managers and the recent contributers get a notification + RestAPI.Content.createComment(nico.restContext, link.id, 'Comment A', null, (err, commentA) => { + assert.ok(!err); + + // Because Bert made a comment previously, he should get a notification as well + ActivityTestsUtil.collectAndGetNotificationStream(bert.restContext, null, (err, activityStream) => { assert.ok(!err); - assert.ok( - _.find(activityStream.items, activity => { - return activity['oae:activityType'] === 'content-comment'; - }) - ); + const commentActivities = _.filter(activityStream.items, activity => { + return activity['oae:activityType'] === 'content-comment'; + }); + assert.ok(commentActivities.length, 2); + // Sanity-check that the managers got it as well ActivityTestsUtil.collectAndGetNotificationStream( nico.restContext, null, (err, activityStream) => { assert.ok(!err); - assert.ok( - _.find(activityStream.items, activity => { - return activity['oae:activityType'] === 'content-comment'; - }) - ); + const commentActivities = _.filter(activityStream.items, activity => { + return activity['oae:activityType'] === 'content-comment'; + }); + assert.ok(commentActivities.length, 2); - // Create another comment and assert that both the managers and the recent contributers get a notification - RestAPI.Content.createComment( - nico.restContext, - link.id, - 'Comment A', + ActivityTestsUtil.collectAndGetNotificationStream( + simon.restContext, null, - (err, commentA) => { + (err, activityStream) => { assert.ok(!err); + const commentActivities = _.filter(activityStream.items, activity => { + return activity['oae:activityType'] === 'content-comment'; + }); + assert.ok(commentActivities.length, 2); - // Because Bert made a comment previously, he should get a notification as well - ActivityTestsUtil.collectAndGetNotificationStream( - bert.restContext, - null, - (err, activityStream) => { - assert.ok(!err); - const commentActivities = _.filter( - activityStream.items, - activity => { - return activity['oae:activityType'] === 'content-comment'; - } - ); - assert.ok(commentActivities.length, 2); - - // Sanity-check that the managers got it as well - ActivityTestsUtil.collectAndGetNotificationStream( - nico.restContext, - null, - (err, activityStream) => { - assert.ok(!err); - const commentActivities = _.filter( - activityStream.items, - activity => { - return activity['oae:activityType'] === 'content-comment'; - } - ); - assert.ok(commentActivities.length, 2); - - ActivityTestsUtil.collectAndGetNotificationStream( - simon.restContext, - null, - (err, activityStream) => { - assert.ok(!err); - const commentActivities = _.filter( - activityStream.items, - activity => { - return ( - activity['oae:activityType'] === 'content-comment' - ); - } - ); - assert.ok(commentActivities.length, 2); - - return callback(); - } - ); - } - ); - } - ); + return callback(); } ); } ); - } - ); - } - ); - } - ); - } - ); + }); + }); + }); + }); + }); + } + ); + }); }); }); @@ -1512,9 +1149,9 @@ describe('Content Activity', () => { */ it('verify the content entity model contains the correct content information', callback => { /*! - * Function used to verify the status of the "static" link content item in this test case. This basically means - * everything except the preview items. - */ + * Function used to verify the status of the "static" link content item in this test case. This basically means + * everything except the preview items. + */ const _assertStandardLinkModel = function(entity, contentId) { const { resourceId } = AuthzUtil.getResourceFromId(contentId); assert.strictEqual(entity['oae:visibility'], 'public'); @@ -1523,10 +1160,7 @@ describe('Content Activity', () => { assert.strictEqual(entity.displayName, 'Google'); assert.strictEqual(entity.objectType, 'content'); assert.strictEqual(entity['oae:id'], contentId); - assert.strictEqual( - entity.url, - 'http://' + global.oaeTests.tenants.cam.host + '/content/camtest/' + resourceId - ); + assert.strictEqual(entity.url, 'http://' + global.oaeTests.tenants.cam.host + '/content/camtest/' + resourceId); assert.ok(entity.id.indexOf(contentId) !== -1); }; @@ -1567,205 +1201,176 @@ describe('Content Activity', () => { assert.ok(!err); // Get the revision ID - RestAPI.Content.getRevisions( - globalTenantAdminRestContext, - link.id, - null, - 1, - (err, revisions) => { - assert.ok(!err); - const { revisionId } = revisions.results[0]; + RestAPI.Content.getRevisions(globalTenantAdminRestContext, link.id, null, 1, (err, revisions) => { + assert.ok(!err); + const { revisionId } = revisions.results[0]; + + // Set the preview to error status + RestAPI.Content.setPreviewItems( + globalTenantAdminRestContext, + link.id, + revisionId, + 'error', + {}, + {}, + {}, + {}, + err => { + assert.ok(!err); - // Set the preview to error status - RestAPI.Content.setPreviewItems( - globalTenantAdminRestContext, - link.id, - revisionId, - 'error', - {}, - {}, - {}, - {}, - err => { - assert.ok(!err); + // Verify that the preview does not display + ActivityTestsUtil.collectAndGetActivityStream( + jack.restContext, + jack.user.id, + null, + (err, activityStream) => { + assert.ok(!err); - // Verify that the preview does not display - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - jack.user.id, - null, - (err, activityStream) => { - assert.ok(!err); + const entity = activityStream.items[0].object; + _assertStandardLinkModel(entity, link.id); + assert.ok(!entity.image); + assert.ok(!entity['oae:wideImage']); - const entity = activityStream.items[0].object; - _assertStandardLinkModel(entity, link.id); - assert.ok(!entity.image); - assert.ok(!entity['oae:wideImage']); - - // Set the preview to ignored status with no files - RestAPI.Content.setPreviewItems( - globalTenantAdminRestContext, - link.id, - revisionId, - 'ignored', - {}, - {}, - {}, - {}, - err => { - assert.ok(!err); + // Set the preview to ignored status with no files + RestAPI.Content.setPreviewItems( + globalTenantAdminRestContext, + link.id, + revisionId, + 'ignored', + {}, + {}, + {}, + {}, + err => { + assert.ok(!err); - // Verify that the preview still does not display - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - jack.user.id, - null, - (err, activityStream) => { - assert.ok(!err); + // Verify that the preview still does not display + ActivityTestsUtil.collectAndGetActivityStream( + jack.restContext, + jack.user.id, + null, + (err, activityStream) => { + assert.ok(!err); - const entity = activityStream.items[0].object; - _assertStandardLinkModel(entity, link.id); - assert.ok(!entity.image); - assert.ok(!entity['oae:wideImage']); - - // Set the preview to done status with files - RestAPI.Content.setPreviewItems( - globalTenantAdminRestContext, - link.id, - revisionId, - 'done', - suitableFiles, - suitableSizes, - {}, - {}, - err => { - assert.ok(!err); + const entity = activityStream.items[0].object; + _assertStandardLinkModel(entity, link.id); + assert.ok(!entity.image); + assert.ok(!entity['oae:wideImage']); + + // Set the preview to done status with files + RestAPI.Content.setPreviewItems( + globalTenantAdminRestContext, + link.id, + revisionId, + 'done', + suitableFiles, + suitableSizes, + {}, + {}, + err => { + assert.ok(!err); - // Verify that the previews are returned in the activity - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - jack.user.id, - null, - (err, activityStream) => { - assert.ok(!err); + // Verify that the previews are returned in the activity + ActivityTestsUtil.collectAndGetActivityStream( + jack.restContext, + jack.user.id, + null, + (err, activityStream) => { + assert.ok(!err); - const entity = activityStream.items[0].object; - _assertStandardLinkModel(entity, link.id); - assert.ok(entity.image); - assert.strictEqual( - entity.image.width, - PreviewConstants.SIZES.IMAGE.THUMBNAIL - ); - assert.strictEqual( - entity.image.height, - PreviewConstants.SIZES.IMAGE.THUMBNAIL - ); - assert.ok(entity.image.url); - assert.ok(entity['oae:wideImage']); - assert.strictEqual( - entity['oae:wideImage'].width, - PreviewConstants.SIZES.IMAGE.WIDE_WIDTH - ); - assert.strictEqual( - entity['oae:wideImage'].height, - PreviewConstants.SIZES.IMAGE.WIDE_HEIGHT - ); - assert.ok(entity['oae:wideImage'].url); - - // Ensure the standard and wide image can be downloaded right now by even an anonymous user on another tenant - let signedDownloadUrl = url.parse( - entity['oae:wideImage'].url, - true - ); - RestUtil.performRestRequest( - anonymousGtRestContext, - signedDownloadUrl.pathname, - 'GET', - signedDownloadUrl.query, - (err, body, response) => { - assert.ok(!err); - assert.strictEqual(response.statusCode, 204); - - signedDownloadUrl = url.parse( - entity.image.url, - true - ); - RestUtil.performRestRequest( - anonymousGtRestContext, - signedDownloadUrl.pathname, - 'GET', - signedDownloadUrl.query, - (err, body, response) => { - assert.ok(!err); - assert.strictEqual( - response.statusCode, - 204 - ); - - // Jump ahead in time by 5 years, test-drive a hovercar and check if the signatures still work - const now = Date.now(); - Date.now = function() { - return ( - now + 5 * 365 * 24 * 60 * 60 * 1000 + const entity = activityStream.items[0].object; + _assertStandardLinkModel(entity, link.id); + assert.ok(entity.image); + assert.strictEqual( + entity.image.width, + PreviewConstants.SIZES.IMAGE.THUMBNAIL + ); + assert.strictEqual( + entity.image.height, + PreviewConstants.SIZES.IMAGE.THUMBNAIL + ); + assert.ok(entity.image.url); + assert.ok(entity['oae:wideImage']); + assert.strictEqual( + entity['oae:wideImage'].width, + PreviewConstants.SIZES.IMAGE.WIDE_WIDTH + ); + assert.strictEqual( + entity['oae:wideImage'].height, + PreviewConstants.SIZES.IMAGE.WIDE_HEIGHT + ); + assert.ok(entity['oae:wideImage'].url); + + // Ensure the standard and wide image can be downloaded right now by even an anonymous user on another tenant + let signedDownloadUrl = url.parse(entity['oae:wideImage'].url, true); + RestUtil.performRestRequest( + anonymousGtRestContext, + signedDownloadUrl.pathname, + 'GET', + signedDownloadUrl.query, + (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 204); + + signedDownloadUrl = url.parse(entity.image.url, true); + RestUtil.performRestRequest( + anonymousGtRestContext, + signedDownloadUrl.pathname, + 'GET', + signedDownloadUrl.query, + (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 204); + + // Jump ahead in time by 5 years, test-drive a hovercar and check if the signatures still work + const now = Date.now(); + Date.now = function() { + return now + 5 * 365 * 24 * 60 * 60 * 1000; + }; + + // Ensure the standard and wide image can still be downloaded 5y in the future by even an anonymous user on another tenant + signedDownloadUrl = url.parse(entity['oae:wideImage'].url, true); + RestUtil.performRestRequest( + anonymousGtRestContext, + signedDownloadUrl.pathname, + 'GET', + signedDownloadUrl.query, + (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 204); + + signedDownloadUrl = url.parse(entity.image.url, true); + RestUtil.performRestRequest( + anonymousGtRestContext, + signedDownloadUrl.pathname, + 'GET', + signedDownloadUrl.query, + (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 204); + + return callback(); + } ); - }; - - // Ensure the standard and wide image can still be downloaded 5y in the future by even an anonymous user on another tenant - signedDownloadUrl = url.parse( - entity['oae:wideImage'].url, - true - ); - RestUtil.performRestRequest( - anonymousGtRestContext, - signedDownloadUrl.pathname, - 'GET', - signedDownloadUrl.query, - (err, body, response) => { - assert.ok(!err); - assert.strictEqual( - response.statusCode, - 204 - ); - - signedDownloadUrl = url.parse( - entity.image.url, - true - ); - RestUtil.performRestRequest( - anonymousGtRestContext, - signedDownloadUrl.pathname, - 'GET', - signedDownloadUrl.query, - (err, body, response) => { - assert.ok(!err); - assert.strictEqual( - response.statusCode, - 204 - ); - - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); } ); } @@ -1796,116 +1401,99 @@ describe('Content Activity', () => { assert.ok(!err); // Create 3 comments, including one reply. We want to make sure the context is properly aggregated in these comments as their activities are delivered. - RestAPI.Content.createComment( - jack.restContext, - link.id, - 'Comment A', - null, - (err, commentA) => { + RestAPI.Content.createComment(jack.restContext, link.id, 'Comment A', null, (err, commentA) => { + assert.ok(!err); + + RestAPI.Content.createComment(jack.restContext, link.id, 'Comment B', null, (err, commentB) => { assert.ok(!err); RestAPI.Content.createComment( jack.restContext, link.id, - 'Comment B', - null, - (err, commentB) => { + 'Reply Comment A', + commentA.created, + (err, replyCommentA) => { assert.ok(!err); - RestAPI.Content.createComment( + ActivityTestsUtil.collectAndGetActivityStream( jack.restContext, - link.id, - 'Reply Comment A', - commentA.created, - (err, replyCommentA) => { + jack.user.id, + null, + (err, activityStream) => { assert.ok(!err); + assert.ok(activityStream); - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - jack.user.id, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok(activityStream); - - // The first in the list (most recent) is the aggregated comment activity - const activity = activityStream.items[0]; - let hadCommentA = false; - let hadCommentB = false; - let hadReplyCommentA = false; - - assert.ok(activity.object['oae:collection']); - assert.strictEqual(activity.object['oae:collection'].length, 3); - - /*! - * Verifies the model of a comment and its context. - * - * @param {ActivityEntity} entity The comment entity to verify - * @param {Comment} comment The comment with which to verify the entity - * @param {Comment} [replyToComment] Indicates the entity should have this comment as its inReplyTo. If unspecified, the entity should have no parent. - */ - const _validateComment = function(entity, comment, replyToComment) { - assert.strictEqual(entity.objectType, 'content-comment'); - assert.strictEqual(entity.content, comment.body); - assert.strictEqual(entity['oae:id'], comment.id); - assert.strictEqual( - entity.url, - '/content/camtest/' + - AuthzUtil.getResourceFromId(comment.messageBoxId).resourceId - ); - assert.ok( - entity.id.indexOf( - 'content/' + link.id + '/messages/' + comment.created - ) !== -1 - ); - assert.strictEqual(entity.published, comment.created); - assert.strictEqual(entity['oae:messageBoxId'], comment.messageBoxId); - assert.strictEqual(entity['oae:threadKey'], comment.threadKey); - - assert.ok(entity.author); - assert.ok(entity.author.objectType, 'user'); - assert.strictEqual(entity.author['oae:id'], comment.createdBy.id); - - if (replyToComment) { - _validateComment(entity.inReplyTo, replyToComment); - } else { - assert.ok(!entity.inReplyTo); - } - }; + // The first in the list (most recent) is the aggregated comment activity + const activity = activityStream.items[0]; + let hadCommentA = false; + let hadCommentB = false; + let hadReplyCommentA = false; + + assert.ok(activity.object['oae:collection']); + assert.strictEqual(activity.object['oae:collection'].length, 3); + + /*! + * Verifies the model of a comment and its context. + * + * @param {ActivityEntity} entity The comment entity to verify + * @param {Comment} comment The comment with which to verify the entity + * @param {Comment} [replyToComment] Indicates the entity should have this comment as its inReplyTo. If unspecified, the entity should have no parent. + */ + const _validateComment = function(entity, comment, replyToComment) { + assert.strictEqual(entity.objectType, 'content-comment'); + assert.strictEqual(entity.content, comment.body); + assert.strictEqual(entity['oae:id'], comment.id); + assert.strictEqual( + entity.url, + '/content/camtest/' + AuthzUtil.getResourceFromId(comment.messageBoxId).resourceId + ); + assert.ok(entity.id.indexOf('content/' + link.id + '/messages/' + comment.created) !== -1); + assert.strictEqual(entity.published, comment.created); + assert.strictEqual(entity['oae:messageBoxId'], comment.messageBoxId); + assert.strictEqual(entity['oae:threadKey'], comment.threadKey); + + assert.ok(entity.author); + assert.ok(entity.author.objectType, 'user'); + assert.strictEqual(entity.author['oae:id'], comment.createdBy.id); + + if (replyToComment) { + _validateComment(entity.inReplyTo, replyToComment); + } else { + assert.ok(!entity.inReplyTo); + } + }; - // Verify that the collection contains all comments, and their models are correct. - activity.object['oae:collection'].forEach(entity => { - if (entity.content === 'Comment A') { - hadCommentA = true; + // Verify that the collection contains all comments, and their models are correct. + activity.object['oae:collection'].forEach(entity => { + if (entity.content === 'Comment A') { + hadCommentA = true; - // Ensures that comment A has correct data, and no parents - _validateComment(entity, commentA); - } else if (entity.content === 'Comment B') { - hadCommentB = true; + // Ensures that comment A has correct data, and no parents + _validateComment(entity, commentA); + } else if (entity.content === 'Comment B') { + hadCommentB = true; - // Ensures that comment B has correct data, and no parents - _validateComment(entity, commentB); - } else if (entity.content === 'Reply Comment A') { - hadReplyCommentA = true; + // Ensures that comment B has correct data, and no parents + _validateComment(entity, commentB); + } else if (entity.content === 'Reply Comment A') { + hadReplyCommentA = true; - // Verify that the reply to comment A has the right data and the parent (comment A) - _validateComment(entity, replyCommentA, commentA); - } - }); + // Verify that the reply to comment A has the right data and the parent (comment A) + _validateComment(entity, replyCommentA, commentA); + } + }); - assert.ok(hadCommentA); - assert.ok(hadCommentB); - assert.ok(hadReplyCommentA); + assert.ok(hadCommentA); + assert.ok(hadCommentB); + assert.ok(hadReplyCommentA); - return callback(); - } - ); + return callback(); } ); } ); - } - ); + }); + }); } ); }); @@ -1918,142 +1506,140 @@ describe('Content Activity', () => { */ it('verify a public, loggedin and private content activity entities are propagated only to appropriate users', callback => { // Create a mix of public, loggedin, private users and groups from public and private tenants - TestsUtil.setupMultiTenantPrivacyEntities( - (publicTenant0, publicTenant1, privateTenant0, privateTenant1) => { - // Follow the publicTenant0.publicUser with the others - const followers = [ - publicTenant0.loggedinUser, - publicTenant0.privateUser, - publicTenant1.publicUser, - publicTenant1.loggedinUser, - publicTenant1.privateUser - ]; - - const publicTenant0PublicUserId = publicTenant0.publicUser.user.id; - const publicTenant0LoggedinUserId = publicTenant0.loggedinUser.user.id; - const publicTenant0PrivateUserId = publicTenant0.privateUser.user.id; - const publicTenant1PublicUserId = publicTenant1.publicUser.user.id; - const publicTenant1LoggedinUserId = publicTenant1.loggedinUser.user.id; - const publicTenant1PrivateUserId = publicTenant1.privateUser.user.id; - - FollowingTestsUtil.followByAll(publicTenant0.publicUser.user.id, followers, () => { - // Create a public, loggedin and private content item to distribute to followers of the actor user - RestAPI.Content.createLink( - publicTenant0.publicUser.restContext, - 'Google', - 'Google', - 'public', - 'http://www.google.ca', - [], - [publicTenant1.publicUser.user.id], - [], - (err, publicLink) => { - assert.ok(!err); - RestAPI.Content.createLink( - publicTenant0.publicUser.restContext, - 'Google', - 'Google', - 'loggedin', - 'http://www.google.ca', - [], - [publicTenant1.publicUser.user.id], - [], - (err, loggedinLink) => { - assert.ok(!err); - RestAPI.Content.createLink( - publicTenant0.publicUser.restContext, - 'Google', - 'Google', - 'private', - 'http://www.google.ca', - [], - [publicTenant1.publicUser.user.id], - [], - (err, privateLink) => { - assert.ok(!err); + TestsUtil.setupMultiTenantPrivacyEntities((publicTenant0, publicTenant1, privateTenant0, privateTenant1) => { + // Follow the publicTenant0.publicUser with the others + const followers = [ + publicTenant0.loggedinUser, + publicTenant0.privateUser, + publicTenant1.publicUser, + publicTenant1.loggedinUser, + publicTenant1.privateUser + ]; + + const publicTenant0PublicUserId = publicTenant0.publicUser.user.id; + const publicTenant0LoggedinUserId = publicTenant0.loggedinUser.user.id; + const publicTenant0PrivateUserId = publicTenant0.privateUser.user.id; + const publicTenant1PublicUserId = publicTenant1.publicUser.user.id; + const publicTenant1LoggedinUserId = publicTenant1.loggedinUser.user.id; + const publicTenant1PrivateUserId = publicTenant1.privateUser.user.id; + + FollowingTestsUtil.followByAll(publicTenant0.publicUser.user.id, followers, () => { + // Create a public, loggedin and private content item to distribute to followers of the actor user + RestAPI.Content.createLink( + publicTenant0.publicUser.restContext, + 'Google', + 'Google', + 'public', + 'http://www.google.ca', + [], + [publicTenant1.publicUser.user.id], + [], + (err, publicLink) => { + assert.ok(!err); + RestAPI.Content.createLink( + publicTenant0.publicUser.restContext, + 'Google', + 'Google', + 'loggedin', + 'http://www.google.ca', + [], + [publicTenant1.publicUser.user.id], + [], + (err, loggedinLink) => { + assert.ok(!err); + RestAPI.Content.createLink( + publicTenant0.publicUser.restContext, + 'Google', + 'Google', + 'private', + 'http://www.google.ca', + [], + [publicTenant1.publicUser.user.id], + [], + (err, privateLink) => { + assert.ok(!err); - // Ensure the user who created them got all 3 content items aggregated in their feed - ActivityTestsUtil.collectAndGetActivityStream( - publicTenant0.publicUser.restContext, - null, - null, - (err, result) => { - assert.ok(!err); - ActivityTestsUtil.assertActivity( - result.items[0], - 'content-create', - 'create', - publicTenant0PublicUserId, - [publicLink.id, loggedinLink.id, privateLink.id], - publicTenant1.publicUser.user.id - ); + // Ensure the user who created them got all 3 content items aggregated in their feed + ActivityTestsUtil.collectAndGetActivityStream( + publicTenant0.publicUser.restContext, + null, + null, + (err, result) => { + assert.ok(!err); + ActivityTestsUtil.assertActivity( + result.items[0], + 'content-create', + 'create', + publicTenant0PublicUserId, + [publicLink.id, loggedinLink.id, privateLink.id], + publicTenant1.publicUser.user.id + ); - // Ensure the loggedin user of the same tenant gets 2 of the content items in their feed: public and loggedin - ActivityTestsUtil.collectAndGetActivityStream( - publicTenant0.loggedinUser.restContext, - null, - null, - (err, result) => { - assert.ok(!err); - ActivityTestsUtil.assertActivity( - result.items[0], - 'content-create', - 'create', - publicTenant0PublicUserId, - [publicLink.id, loggedinLink.id], - publicTenant1.publicUser.user.id - ); + // Ensure the loggedin user of the same tenant gets 2 of the content items in their feed: public and loggedin + ActivityTestsUtil.collectAndGetActivityStream( + publicTenant0.loggedinUser.restContext, + null, + null, + (err, result) => { + assert.ok(!err); + ActivityTestsUtil.assertActivity( + result.items[0], + 'content-create', + 'create', + publicTenant0PublicUserId, + [publicLink.id, loggedinLink.id], + publicTenant1.publicUser.user.id + ); - // Ensure the public user from another tenant gets all 3 of the content items in their feed because they were made a member. This - // ensures that even if the tenant propagation fails on the content item, the association propagation still includes them - ActivityTestsUtil.collectAndGetActivityStream( - publicTenant1.publicUser.restContext, - null, - null, - (err, result) => { - assert.ok(!err); - ActivityTestsUtil.assertActivity( - result.items[0], - 'content-create', - 'create', - publicTenant0PublicUserId, - [publicLink.id, loggedinLink.id, privateLink.id], - publicTenant1.publicUser.user.id - ); + // Ensure the public user from another tenant gets all 3 of the content items in their feed because they were made a member. This + // ensures that even if the tenant propagation fails on the content item, the association propagation still includes them + ActivityTestsUtil.collectAndGetActivityStream( + publicTenant1.publicUser.restContext, + null, + null, + (err, result) => { + assert.ok(!err); + ActivityTestsUtil.assertActivity( + result.items[0], + 'content-create', + 'create', + publicTenant0PublicUserId, + [publicLink.id, loggedinLink.id, privateLink.id], + publicTenant1.publicUser.user.id + ); - // Ensure the loggedin user from another tenant only gets the public content item since they are not a member and cannot see the loggedin one - ActivityTestsUtil.collectAndGetActivityStream( - publicTenant1.loggedinUser.restContext, - null, - null, - (err, result) => { - assert.ok(!err); - ActivityTestsUtil.assertActivity( - result.items[0], - 'content-create', - 'create', - publicTenant0PublicUserId, - publicLink.id, - publicTenant1.publicUser.user.id - ); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - }); - } - ); + // Ensure the loggedin user from another tenant only gets the public content item since they are not a member and cannot see the loggedin one + ActivityTestsUtil.collectAndGetActivityStream( + publicTenant1.loggedinUser.restContext, + null, + null, + (err, result) => { + assert.ok(!err); + ActivityTestsUtil.assertActivity( + result.items[0], + 'content-create', + 'create', + publicTenant0PublicUserId, + publicLink.id, + publicTenant1.publicUser.user.id + ); + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); + }); }); }); @@ -2078,40 +1664,25 @@ describe('Content Activity', () => { (err, link) => { assert.ok(!err); - RestAPI.Content.updateContent( - jack.restContext, - link.id, - { description: 'Super awesome link' }, - err => { - assert.ok(!err); + RestAPI.Content.updateContent(jack.restContext, link.id, { description: 'Super awesome link' }, err => { + assert.ok(!err); - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - jack.user.id, - null, - (err, activityStream) => { - assert.ok(!err); - const createActivity = _getActivity( - activityStream, - 'content-create', - 'object', - link.id - ); - const updateActivity = _getActivity( - activityStream, - 'content-update', - 'object', - link.id - ); - assert.ok(createActivity); - assert.strictEqual(createActivity.verb, 'create'); - assert.ok(updateActivity); - assert.strictEqual(updateActivity.verb, 'update'); - return callback(); - } - ); - } - ); + ActivityTestsUtil.collectAndGetActivityStream( + jack.restContext, + jack.user.id, + null, + (err, activityStream) => { + assert.ok(!err); + const createActivity = _getActivity(activityStream, 'content-create', 'object', link.id); + const updateActivity = _getActivity(activityStream, 'content-update', 'object', link.id); + assert.ok(createActivity); + assert.strictEqual(createActivity.verb, 'create'); + assert.ok(updateActivity); + assert.strictEqual(updateActivity.verb, 'update'); + return callback(); + } + ); + }); } ); }); @@ -2150,19 +1721,9 @@ describe('Content Activity', () => { null, (err, activityStream) => { assert.ok(!err); - const createActivity = _getActivity( - activityStream, - 'content-create', - 'object', - content.id - ); + const createActivity = _getActivity(activityStream, 'content-create', 'object', content.id); - const updateActivity = _getActivity( - activityStream, - 'content-revision', - 'object', - content.id - ); + const updateActivity = _getActivity(activityStream, 'content-revision', 'object', content.id); assert.ok(createActivity); assert.ok(updateActivity); assert.notStrictEqual( @@ -2208,12 +1769,7 @@ describe('Content Activity', () => { null, (err, activityStream) => { assert.ok(!err); - const shareActivity = _getActivity( - activityStream, - 'content-share', - 'object', - link.id - ); + const shareActivity = _getActivity(activityStream, 'content-share', 'object', link.id); assert.ok(shareActivity); assert.strictEqual(shareActivity.verb, 'share'); callback(); @@ -2311,19 +1867,12 @@ describe('Content Activity', () => { null, (err, activityStream) => { assert.ok(!err); - const revisionActivity = _getActivity( - activityStream, - 'content-revision', - 'object', - content.id - ); + const revisionActivity = _getActivity(activityStream, 'content-revision', 'object', content.id); assert.ok(revisionActivity); assert.strictEqual(revisionActivity.verb, 'update'); // Also verify that a content-update activity *doesn't* get generated. no one will want to see both a revision and a meta-data update - assert.ok( - !_getActivity(activityStream, 'content-update', 'object', content.id) - ); + assert.ok(!_getActivity(activityStream, 'content-update', 'object', content.id)); return callback(); } @@ -2364,12 +1913,7 @@ describe('Content Activity', () => { null, (err, activityStream) => { assert.ok(!err); - const addActivity = _getActivity( - activityStream, - 'content-add-to-library', - 'object', - link.id - ); + const addActivity = _getActivity(activityStream, 'content-add-to-library', 'object', link.id); assert.ok(addActivity); assert.strictEqual(addActivity.verb, 'add'); callback(); @@ -2401,32 +1945,27 @@ describe('Content Activity', () => { assert.ok(!err); // Jack adds the content item to his own library - RestAPI.Content.updateContent( - camAdminRestContext, - link.id, - { visibility: 'private' }, - err => { - assert.ok(!err); + RestAPI.Content.updateContent(camAdminRestContext, link.id, { visibility: 'private' }, err => { + assert.ok(!err); - ActivityTestsUtil.collectAndGetActivityStream( - camAdminRestContext, - jack.user.id, - null, - (err, activityStream) => { - assert.ok(!err); - const updateVisibilityActivity = _getActivity( - activityStream, - 'content-update-visibility', - 'object', - link.id - ); - assert.ok(updateVisibilityActivity); - assert.strictEqual(updateVisibilityActivity.verb, 'update'); - callback(); - } - ); - } - ); + ActivityTestsUtil.collectAndGetActivityStream( + camAdminRestContext, + jack.user.id, + null, + (err, activityStream) => { + assert.ok(!err); + const updateVisibilityActivity = _getActivity( + activityStream, + 'content-update-visibility', + 'object', + link.id + ); + assert.ok(updateVisibilityActivity); + assert.strictEqual(updateVisibilityActivity.verb, 'update'); + callback(); + } + ); + }); } ); }); @@ -2473,44 +2012,34 @@ describe('Content Activity', () => { assert.ok(!err); // Verify the activities were aggregated into one, pivoted by actor - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok(activityStream); - assert.strictEqual(activityStream.items.length, 1); - - const aggregate = _getActivity( - activityStream, - 'content-create', - 'actor', - jack.user.id + ActivityTestsUtil.collectAndGetActivityStream(jack.restContext, null, null, (err, activityStream) => { + assert.ok(!err); + assert.ok(activityStream); + assert.strictEqual(activityStream.items.length, 1); + + const aggregate = _getActivity(activityStream, 'content-create', 'actor', jack.user.id); + assert.ok(aggregate.object); + assert.ok(aggregate.object['oae:collection']); + assert.strictEqual(aggregate.object['oae:collection'].length, 2); + + if ( + aggregate.object['oae:collection'][0]['oae:id'] === googleLink.id && + aggregate.object['oae:collection'][1]['oae:id'] === yahooLink.id + ) { + // Don't fail, we want one to be google and the other to be yahoo + } else if ( + aggregate.object['oae:collection'][0]['oae:id'] === yahooLink.id && + aggregate.object['oae:collection'][1]['oae:id'] === googleLink.id + ) { + // Don't fail, we want one to be google and the other to be yahoo + } else { + assert.fail( + 'Expected the collection of created content items to be one yahoo link and one google link.' ); - assert.ok(aggregate.object); - assert.ok(aggregate.object['oae:collection']); - assert.strictEqual(aggregate.object['oae:collection'].length, 2); - - if ( - aggregate.object['oae:collection'][0]['oae:id'] === googleLink.id && - aggregate.object['oae:collection'][1]['oae:id'] === yahooLink.id - ) { - // Don't fail, we want one to be google and the other to be yahoo - } else if ( - aggregate.object['oae:collection'][0]['oae:id'] === yahooLink.id && - aggregate.object['oae:collection'][1]['oae:id'] === googleLink.id - ) { - // Don't fail, we want one to be google and the other to be yahoo - } else { - assert.fail( - 'Expected the collection of created content items to be one yahoo link and one google link.' - ); - } - - callback(); } - ); + + callback(); + }); } ); } @@ -2540,71 +2069,61 @@ describe('Content Activity', () => { assert.ok(!err); // Force a collection of activities so that the individual activity is delivered - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok(activityStream); - assert.ok(activityStream.items.length, 1); + ActivityTestsUtil.collectAndGetActivityStream(jack.restContext, null, null, (err, activityStream) => { + assert.ok(!err); + assert.ok(activityStream); + assert.ok(activityStream.items.length, 1); - // Create a Yahoo link - RestAPI.Content.createLink( - jack.restContext, - 'Yahoo!', - 'Yahoo!', - 'public', - 'http://www.yahoo.ca', - [], - [], - [], - (err, yahooLink) => { + // Create a Yahoo link + RestAPI.Content.createLink( + jack.restContext, + 'Yahoo!', + 'Yahoo!', + 'public', + 'http://www.yahoo.ca', + [], + [], + [], + (err, yahooLink) => { + assert.ok(!err); + + // Collect again and ensure we still only have one activity + ActivityTestsUtil.collectAndGetActivityStream(jack.restContext, null, null, (err, activityStream) => { assert.ok(!err); + assert.ok(activityStream); + assert.ok(activityStream.items.length, 1); - // Collect again and ensure we still only have one activity - ActivityTestsUtil.collectAndGetActivityStream( + // Rinse and repeat once to ensure that the aggregates are removed properly as well + RestAPI.Content.createLink( jack.restContext, - null, - null, - (err, activityStream) => { + 'Apereo!', + 'Apereo!', + 'public', + 'http://www.apereo.org', + [], + [], + [], + (err, apereoLink) => { assert.ok(!err); - assert.ok(activityStream); - assert.ok(activityStream.items.length, 1); - // Rinse and repeat once to ensure that the aggregates are removed properly as well - RestAPI.Content.createLink( + // Collect again and ensure we still only have one activity + ActivityTestsUtil.collectAndGetActivityStream( jack.restContext, - 'Apereo!', - 'Apereo!', - 'public', - 'http://www.apereo.org', - [], - [], - [], - (err, apereoLink) => { + null, + null, + (err, activityStream) => { assert.ok(!err); - - // Collect again and ensure we still only have one activity - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok(activityStream); - assert.ok(activityStream.items.length, 1); - callback(); - } - ); + assert.ok(activityStream); + assert.ok(activityStream.items.length, 1); + callback(); } ); } ); - } - ); - } - ); + }); + } + ); + }); } ); }); @@ -3038,54 +2557,34 @@ describe('Content Activity', () => { assert.ok(!err); // Update the content once as jack - RestAPI.Content.updateContent( - jack.restContext, - googleLink.id, - { displayName: 'The Google' }, - err => { - assert.ok(!err); + RestAPI.Content.updateContent(jack.restContext, googleLink.id, { displayName: 'The Google' }, err => { + assert.ok(!err); - // Update it a second time as jack, we use this to make sure we don't get duplicates in the aggregation - RestAPI.Content.updateContent( - jack.restContext, - googleLink.id, - { displayName: 'Google' }, - err => { - assert.ok(!err); + // Update it a second time as jack, we use this to make sure we don't get duplicates in the aggregation + RestAPI.Content.updateContent(jack.restContext, googleLink.id, { displayName: 'Google' }, err => { + assert.ok(!err); - // Update it with a different user, this should be a second entry in the collection - RestAPI.Content.updateContent( - camAdminRestContext, - googleLink.id, - { displayName: 'Google' }, - err => { - assert.ok(!err); + // Update it with a different user, this should be a second entry in the collection + RestAPI.Content.updateContent(camAdminRestContext, googleLink.id, { displayName: 'Google' }, err => { + assert.ok(!err); - // Verify we get the 2 actors in the stream - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok(activityStream); + // Verify we get the 2 actors in the stream + ActivityTestsUtil.collectAndGetActivityStream(jack.restContext, null, null, (err, activityStream) => { + assert.ok(!err); + assert.ok(activityStream); - const activity = activityStream.items[0]; - assert.ok(activity); - assert.strictEqual(activity['oae:activityType'], 'content-update'); + const activity = activityStream.items[0]; + assert.ok(activity); + assert.strictEqual(activity['oae:activityType'], 'content-update'); - const actors = activity.actor['oae:collection']; - assert.ok(actors); - assert.strictEqual(actors.length, 2); - callback(); - } - ); - } - ); - } - ); - } - ); + const actors = activity.actor['oae:collection']; + assert.ok(actors); + assert.strictEqual(actors.length, 2); + callback(); + }); + }); + }); + }); } ); }); @@ -3113,135 +2612,115 @@ describe('Content Activity', () => { assert.ok(!err); // Update the content once as jack - RestAPI.Content.updateContent( - jack.restContext, - googleLink.id, - { displayName: 'The Google' }, - err => { - assert.ok(!err); + RestAPI.Content.updateContent(jack.restContext, googleLink.id, { displayName: 'The Google' }, err => { + assert.ok(!err); - // Add something to the activity feed that happened later than the previous update - RestAPI.Content.createLink( - jack.restContext, - 'Yahoo!', - 'Yahoo!', - 'public', - 'http://www.yahoo.ca', - [], - [], - [], - (err, yahooLink) => { + // Add something to the activity feed that happened later than the previous update + RestAPI.Content.createLink( + jack.restContext, + 'Yahoo!', + 'Yahoo!', + 'public', + 'http://www.yahoo.ca', + [], + [], + [], + (err, yahooLink) => { + assert.ok(!err); + + // Update it a second time as jack, we use this to make sure we don't get duplicates in the aggregation, and ensure the update jumps ahead of the last create activity in the feed + RestAPI.Content.updateContent(jack.restContext, googleLink.id, { displayName: 'Google' }, err => { assert.ok(!err); - // Update it a second time as jack, we use this to make sure we don't get duplicates in the aggregation, and ensure the update jumps ahead of the last create activity in the feed - RestAPI.Content.updateContent( + // Verify that the activity is still a non-aggregated activity, it just jumped to the front of the feed + ActivityTestsUtil.collectAndGetActivityStream( jack.restContext, - googleLink.id, - { displayName: 'Google' }, - err => { + null, + null, + (err, activityStream) => { assert.ok(!err); + assert.ok(activityStream); - // Verify that the activity is still a non-aggregated activity, it just jumped to the front of the feed - ActivityTestsUtil.collectAndGetActivityStream( + // One for the "content-create" aggregation, one for the "update content" duplicates + assert.ok(activityStream.items.length, 2); + + // Ensures that the actor is not a collection, but still an individual entity + const activity = activityStream.items[0]; + assert.strictEqual(activity['oae:activityType'], 'content-update'); + assert.ok(activity.actor['oae:id'], jack.user.id); + assert.strictEqual( + activity.actor['oae:profilePath'], + '/user/' + jack.user.tenant.alias + '/' + AuthzUtil.getResourceFromId(jack.user.id).resourceId + ); + assert.ok(activity.object['oae:id'], googleLink.id); + assert.strictEqual( + activity.object['oae:profilePath'], + '/content/' + + googleLink.tenant.alias + + '/' + + AuthzUtil.getResourceFromId(googleLink.id).resourceId + ); + + // Send a new activity into the feed so it is the most recent + RestAPI.Content.createLink( jack.restContext, - null, - null, - (err, activityStream) => { + 'Apereo', + 'Apereo', + 'public', + 'http://www.apereo.org', + [], + [], + [], + (err, apereoLink) => { assert.ok(!err); - assert.ok(activityStream); - // One for the "content-create" aggregation, one for the "update content" duplicates - assert.ok(activityStream.items.length, 2); - - // Ensures that the actor is not a collection, but still an individual entity - const activity = activityStream.items[0]; - assert.strictEqual(activity['oae:activityType'], 'content-update'); - assert.ok(activity.actor['oae:id'], jack.user.id); - assert.strictEqual( - activity.actor['oae:profilePath'], - '/user/' + - jack.user.tenant.alias + - '/' + - AuthzUtil.getResourceFromId(jack.user.id).resourceId - ); - assert.ok(activity.object['oae:id'], googleLink.id); - assert.strictEqual( - activity.object['oae:profilePath'], - '/content/' + - googleLink.tenant.alias + - '/' + - AuthzUtil.getResourceFromId(googleLink.id).resourceId - ); - - // Send a new activity into the feed so it is the most recent - RestAPI.Content.createLink( + // Force a collection so that the most recent activity is in the feed + ActivityTestsUtil.collectAndGetActivityStream( jack.restContext, - 'Apereo', - 'Apereo', - 'public', - 'http://www.apereo.org', - [], - [], - [], - (err, apereoLink) => { + null, + null, + (err, activityStream) => { assert.ok(!err); + assert.ok(activityStream); + assert.strictEqual(activityStream.items.length, 2); - // Force a collection so that the most recent activity is in the feed - ActivityTestsUtil.collectAndGetActivityStream( + // Jump the update activity to the top again + RestAPI.Content.updateContent( jack.restContext, - null, - null, - (err, activityStream) => { + googleLink.id, + { displayName: 'Google' }, + err => { assert.ok(!err); - assert.ok(activityStream); - assert.strictEqual(activityStream.items.length, 2); - // Jump the update activity to the top again - RestAPI.Content.updateContent( + // Verify update activity is at the top and still an individual activity + ActivityTestsUtil.collectAndGetActivityStream( jack.restContext, - googleLink.id, - { displayName: 'Google' }, - err => { - assert.ok(!err); + null, + null, + (err, activityStream) => { + assert.ok(activityStream); + assert.strictEqual(activityStream.items.length, 2); - // Verify update activity is at the top and still an individual activity - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { - assert.ok(activityStream); - assert.strictEqual(activityStream.items.length, 2); - - // Content-update activity should be the first in the list, it should still be a single activity - const activity = activityStream.items[0]; - assert.strictEqual( - activity['oae:activityType'], - 'content-update' - ); - assert.strictEqual( - activity.actor['oae:id'], - jack.user.id - ); - assert.strictEqual( - activity.actor['oae:profilePath'], - '/user/' + - jack.user.tenant.alias + - '/' + - AuthzUtil.getResourceFromId(jack.user.id).resourceId - ); - assert.ok(activity.object['oae:id'], googleLink.id); - assert.strictEqual( - activity.object['oae:profilePath'], - '/content/' + - googleLink.tenant.alias + - '/' + - AuthzUtil.getResourceFromId(googleLink.id) - .resourceId - ); - callback(); - } + // Content-update activity should be the first in the list, it should still be a single activity + const activity = activityStream.items[0]; + assert.strictEqual(activity['oae:activityType'], 'content-update'); + assert.strictEqual(activity.actor['oae:id'], jack.user.id); + assert.strictEqual( + activity.actor['oae:profilePath'], + '/user/' + + jack.user.tenant.alias + + '/' + + AuthzUtil.getResourceFromId(jack.user.id).resourceId ); + assert.ok(activity.object['oae:id'], googleLink.id); + assert.strictEqual( + activity.object['oae:profilePath'], + '/content/' + + googleLink.tenant.alias + + '/' + + AuthzUtil.getResourceFromId(googleLink.id).resourceId + ); + callback(); } ); } @@ -3251,11 +2730,11 @@ describe('Content Activity', () => { } ); } - ); - } - ); - } - ); + ); + }); + } + ); + }); } ); }); @@ -3266,10 +2745,10 @@ describe('Content Activity', () => { /// //////////////////////////// /* - * The "content-update-visibility" activity demonstrates an activity that has no pivot points, therefore it should be - * treated as though it pivots on all three entities (actor, object, target) such that duplicate activities within the - * aggregation period are not posted multiple times. Duplicates are instead pushed to the top of the feed. - */ + * The "content-update-visibility" activity demonstrates an activity that has no pivot points, therefore it should be + * treated as though it pivots on all three entities (actor, object, target) such that duplicate activities within the + * aggregation period are not posted multiple times. Duplicates are instead pushed to the top of the feed. + */ /** * Test that verifies when a content-update-visibility activity is posted duplicate times, it does not result in multiple entries in the @@ -3293,138 +2772,115 @@ describe('Content Activity', () => { assert.ok(!err); // Update the content once as jack - RestAPI.Content.updateContent( - jack.restContext, - googleLink.id, - { visibility: 'loggedin' }, - err => { - assert.ok(!err); + RestAPI.Content.updateContent(jack.restContext, googleLink.id, { visibility: 'loggedin' }, err => { + assert.ok(!err); - // Add something to the activity feed that happened later than the previous update - RestAPI.Content.createLink( - jack.restContext, - 'Yahoo!', - 'Yahoo!', - 'public', - 'http://www.yahoo.ca', - [], - [], - [], - (err, yahooLink) => { + // Add something to the activity feed that happened later than the previous update + RestAPI.Content.createLink( + jack.restContext, + 'Yahoo!', + 'Yahoo!', + 'public', + 'http://www.yahoo.ca', + [], + [], + [], + (err, yahooLink) => { + assert.ok(!err); + + // Update it a second time as jack, we use this to make sure we don't get duplicates in the aggregation, and ensure the update jumps ahead of the last create activity in the feed + RestAPI.Content.updateContent(jack.restContext, googleLink.id, { visibility: 'private' }, err => { assert.ok(!err); - // Update it a second time as jack, we use this to make sure we don't get duplicates in the aggregation, and ensure the update jumps ahead of the last create activity in the feed - RestAPI.Content.updateContent( + // Verify that the activity is still a non-aggregated activity, it just jumped to the front of the feed + ActivityTestsUtil.collectAndGetActivityStream( jack.restContext, - googleLink.id, - { visibility: 'private' }, - err => { + null, + null, + (err, activityStream) => { assert.ok(!err); + assert.ok(activityStream); - // Verify that the activity is still a non-aggregated activity, it just jumped to the front of the feed - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok(activityStream); + // One for the "content-create" aggregation, one for the "update content" duplicates + assert.ok(activityStream.items.length, 2); - // One for the "content-create" aggregation, one for the "update content" duplicates - assert.ok(activityStream.items.length, 2); + // Ensures that the actor is not a collection, but still an individual entity + const activity = activityStream.items[0]; + assert.strictEqual(activity['oae:activityType'], 'content-update-visibility'); + assert.ok(activity.actor['oae:id'], jack.user.id); + assert.strictEqual( + activity.actor['oae:profilePath'], + '/user/' + jack.user.tenant.alias + '/' + AuthzUtil.getResourceFromId(jack.user.id).resourceId + ); + assert.ok(activity.object['oae:id'], googleLink.id); + assert.strictEqual( + activity.object['oae:profilePath'], + '/content/' + + googleLink.tenant.alias + + '/' + + AuthzUtil.getResourceFromId(googleLink.id).resourceId + ); - // Ensures that the actor is not a collection, but still an individual entity - const activity = activityStream.items[0]; - assert.strictEqual( - activity['oae:activityType'], - 'content-update-visibility' - ); - assert.ok(activity.actor['oae:id'], jack.user.id); - assert.strictEqual( - activity.actor['oae:profilePath'], - '/user/' + - jack.user.tenant.alias + - '/' + - AuthzUtil.getResourceFromId(jack.user.id).resourceId - ); - assert.ok(activity.object['oae:id'], googleLink.id); - assert.strictEqual( - activity.object['oae:profilePath'], - '/content/' + - googleLink.tenant.alias + - '/' + - AuthzUtil.getResourceFromId(googleLink.id).resourceId - ); + // Send a new activity into the feed so it is the most recent + RestAPI.Content.createLink( + jack.restContext, + 'Apereo', + 'Apereo', + 'public', + 'http://www.apereo.org', + [], + [], + [], + (err, apereoLink) => { + assert.ok(!err); - // Send a new activity into the feed so it is the most recent - RestAPI.Content.createLink( + // Force a collection so that the most recent activity is in the feed + ActivityTestsUtil.collectAndGetActivityStream( jack.restContext, - 'Apereo', - 'Apereo', - 'public', - 'http://www.apereo.org', - [], - [], - [], - (err, apereoLink) => { + null, + null, + (err, activityStream) => { assert.ok(!err); + assert.ok(activityStream); + assert.strictEqual(activityStream.items.length, 2); - // Force a collection so that the most recent activity is in the feed - ActivityTestsUtil.collectAndGetActivityStream( + // Jump the update activity to the top again + RestAPI.Content.updateContent( jack.restContext, - null, - null, - (err, activityStream) => { + googleLink.id, + { visibility: 'public' }, + err => { assert.ok(!err); - assert.ok(activityStream); - assert.strictEqual(activityStream.items.length, 2); - // Jump the update activity to the top again - RestAPI.Content.updateContent( + // Verify update activity is at the top and still an individual activity + ActivityTestsUtil.collectAndGetActivityStream( jack.restContext, - googleLink.id, - { visibility: 'public' }, - err => { - assert.ok(!err); + null, + null, + (err, activityStream) => { + assert.ok(activityStream); + assert.strictEqual(activityStream.items.length, 2); - // Verify update activity is at the top and still an individual activity - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { - assert.ok(activityStream); - assert.strictEqual(activityStream.items.length, 2); - - // Content-update activity should be the first in the list, it should still be a single activity - const activity = activityStream.items[0]; - assert.strictEqual( - activity['oae:activityType'], - 'content-update-visibility' - ); - assert.strictEqual( - activity.actor['oae:id'], - jack.user.id - ); - assert.strictEqual( - activity.actor['oae:profilePath'], - '/user/' + - jack.user.tenant.alias + - '/' + - AuthzUtil.getResourceFromId(jack.user.id).resourceId - ); - assert.ok(activity.object['oae:id'], googleLink.id); - assert.strictEqual( - activity.object['oae:profilePath'], - '/content/' + - googleLink.tenant.alias + - '/' + - AuthzUtil.getResourceFromId(googleLink.id) - .resourceId - ); - callback(); - } + // Content-update activity should be the first in the list, it should still be a single activity + const activity = activityStream.items[0]; + assert.strictEqual(activity['oae:activityType'], 'content-update-visibility'); + assert.strictEqual(activity.actor['oae:id'], jack.user.id); + assert.strictEqual( + activity.actor['oae:profilePath'], + '/user/' + + jack.user.tenant.alias + + '/' + + AuthzUtil.getResourceFromId(jack.user.id).resourceId ); + assert.ok(activity.object['oae:id'], googleLink.id); + assert.strictEqual( + activity.object['oae:profilePath'], + '/content/' + + googleLink.tenant.alias + + '/' + + AuthzUtil.getResourceFromId(googleLink.id).resourceId + ); + callback(); } ); } @@ -3435,10 +2891,10 @@ describe('Content Activity', () => { ); } ); - } - ); - } - ); + }); + } + ); + }); } ); }); @@ -3449,9 +2905,9 @@ describe('Content Activity', () => { /// ////////////////// /* - * The content-comment activity demonstrates an activity that has all 3 entities, but only aggregates on 1 of them. This means that - * 2 of the entity types are aggregated as more activities are generated instead of just one. - */ + * The content-comment activity demonstrates an activity that has all 3 entities, but only aggregates on 1 of them. This means that + * 2 of the entity types are aggregated as more activities are generated instead of just one. + */ /** * Test that verifies that when content-comment activities are aggregated, both actor and object entities are collected into the activity @@ -3475,24 +2931,36 @@ describe('Content Activity', () => { assert.ok(!err); // Post a content as jack - RestAPI.Content.createComment( - jack.restContext, - link.id, - 'Test Comment A', - null, - err => { + RestAPI.Content.createComment(jack.restContext, link.id, 'Test Comment A', null, err => { + assert.ok(!err); + + // Post a comment as the cambridge admin, we have now aggregated a 2nd comment posting on the same content item + RestAPI.Content.createComment(camAdminRestContext, link.id, 'Test Comment B', null, err => { assert.ok(!err); - // Post a comment as the cambridge admin, we have now aggregated a 2nd comment posting on the same content item - RestAPI.Content.createComment( - camAdminRestContext, - link.id, - 'Test Comment B', - null, - err => { + // Verify that both actors (camadmin and jack) and both objects (both comments) are available in the activity + ActivityTestsUtil.collectAndGetActivityStream(jack.restContext, null, null, (err, activityStream) => { + assert.ok(!err); + assert.ok(activityStream); + assert.strictEqual(activityStream.items.length, 2); + + const activity = activityStream.items[0]; + assert.strictEqual(activity['oae:activityType'], 'content-comment'); + + // Ensure we've aggregated all actors and objects + const actors = activity.actor['oae:collection']; + const objects = activity.object['oae:collection']; + assert.ok(actors); + assert.ok(objects); + assert.strictEqual(actors.length, 2); + assert.strictEqual(objects.length, 2); + assert.strictEqual(activity.target['oae:id'], link.id); + + // Post a 3rd comment as a user who has posted already + RestAPI.Content.createComment(jack.restContext, link.id, 'Test Comment C', null, err => { assert.ok(!err); - // Verify that both actors (camadmin and jack) and both objects (both comments) are available in the activity + // Verify that the 3rd comment is aggregated into the object collection of the activity, however the actor collection has only the 2 unique actors ActivityTestsUtil.collectAndGetActivityStream( jack.restContext, null, @@ -3505,56 +2973,21 @@ describe('Content Activity', () => { const activity = activityStream.items[0]; assert.strictEqual(activity['oae:activityType'], 'content-comment'); - // Ensure we've aggregated all actors and objects + // Ensure we now have one additional object, but we should still only have 2 users because it was the same user that posted the 3rd time const actors = activity.actor['oae:collection']; const objects = activity.object['oae:collection']; assert.ok(actors); assert.ok(objects); assert.strictEqual(actors.length, 2); - assert.strictEqual(objects.length, 2); + assert.strictEqual(objects.length, 3); assert.strictEqual(activity.target['oae:id'], link.id); - - // Post a 3rd comment as a user who has posted already - RestAPI.Content.createComment( - jack.restContext, - link.id, - 'Test Comment C', - null, - err => { - assert.ok(!err); - - // Verify that the 3rd comment is aggregated into the object collection of the activity, however the actor collection has only the 2 unique actors - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok(activityStream); - assert.strictEqual(activityStream.items.length, 2); - - const activity = activityStream.items[0]; - assert.strictEqual(activity['oae:activityType'], 'content-comment'); - - // Ensure we now have one additional object, but we should still only have 2 users because it was the same user that posted the 3rd time - const actors = activity.actor['oae:collection']; - const objects = activity.object['oae:collection']; - assert.ok(actors); - assert.ok(objects); - assert.strictEqual(actors.length, 2); - assert.strictEqual(objects.length, 3); - assert.strictEqual(activity.target['oae:id'], link.id); - callback(); - } - ); - } - ); + callback(); } ); - } - ); - } - ); + }); + }); + }); + }); } ); }); @@ -3565,12 +2998,12 @@ describe('Content Activity', () => { /// //////////////// /* - * The content-share activity demonstrates a case where you have 2 pivots for a single activity. - * - * One pivot is actor+object, which enables the aggregation: "Branden Visser shared Mythology with 4 users and groups" - * - * The other pivot is actor+target, which enables the aggregation: "Branden Visser shared 5 items with GroupA" - */ + * The content-share activity demonstrates a case where you have 2 pivots for a single activity. + * + * One pivot is actor+object, which enables the aggregation: "Branden Visser shared Mythology with 4 users and groups" + * + * The other pivot is actor+target, which enables the aggregation: "Branden Visser shared 5 items with GroupA" + */ /** * Test that verifies that duplicating an activity that has multiple pivots does not result in redundant data in the @@ -3645,10 +3078,7 @@ describe('Content Activity', () => { assert.strictEqual(activity.object['oae:id'], link.id); assert.strictEqual( activity.object['oae:profilePath'], - '/content/' + - link.tenant.alias + - '/' + - AuthzUtil.getResourceFromId(link.id).resourceId + '/content/' + link.tenant.alias + '/' + AuthzUtil.getResourceFromId(link.id).resourceId ); assert.strictEqual(activity.target['oae:id'], jane.user.id); assert.strictEqual( @@ -3660,83 +3090,61 @@ describe('Content Activity', () => { ); // Repeat once more to ensure we don't duplicate when the aggregate is already active - RestAPI.Content.updateMembers( - jack.restContext, - link.id, - removeJane, - err => { - assert.ok(!err); + RestAPI.Content.updateMembers(jack.restContext, link.id, removeJane, err => { + assert.ok(!err); + + // Create some noise in the feed to ensure that the third share content will jump to the top + RestAPI.Content.createLink( + jack.restContext, + 'Apereo', + 'Apereo', + 'public', + 'http://www.apereo.org', + [], + [], + [], + (err, apereoLink) => { + assert.ok(!err); - // Create some noise in the feed to ensure that the third share content will jump to the top - RestAPI.Content.createLink( - jack.restContext, - 'Apereo', - 'Apereo', - 'public', - 'http://www.apereo.org', - [], - [], - [], - (err, apereoLink) => { + // Re-share with Jane for the 3rd time + RestAPI.Content.shareContent(jack.restContext, link.id, [jane.user.id], err => { assert.ok(!err); - // Re-share with Jane for the 3rd time - RestAPI.Content.shareContent( + // Verify that jack still has only one activity in his feed representing the content-share + ActivityTestsUtil.collectAndGetActivityStream( jack.restContext, - link.id, - [jane.user.id], - err => { + null, + null, + (err, activityStream) => { assert.ok(!err); + assert.ok(activityStream); + assert.strictEqual(activityStream.items.length, 2); - // Verify that jack still has only one activity in his feed representing the content-share - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok(activityStream); - assert.strictEqual(activityStream.items.length, 2); - - // The first activity should be the content share, and it should still not be an aggregation - const activity = activityStream.items[0]; - assert.strictEqual( - activity['oae:activityType'], - 'content-share' - ); - assert.strictEqual( - activity.actor['oae:id'], - jack.user.id - ); - assert.strictEqual( - activity.actor['oae:profilePath'], - '/user/camtest/' + - AuthzUtil.getResourceFromId(jack.user.id).resourceId - ); - assert.strictEqual(activity.object['oae:id'], link.id); - assert.strictEqual( - activity.object['oae:profilePath'], - '/content/camtest/' + - AuthzUtil.getResourceFromId(link.id).resourceId - ); - assert.strictEqual( - activity.target['oae:id'], - jane.user.id - ); - assert.strictEqual( - activity.target['oae:profilePath'], - '/user/camtest/' + - AuthzUtil.getResourceFromId(jane.user.id).resourceId - ); - return callback(); - } + // The first activity should be the content share, and it should still not be an aggregation + const activity = activityStream.items[0]; + assert.strictEqual(activity['oae:activityType'], 'content-share'); + assert.strictEqual(activity.actor['oae:id'], jack.user.id); + assert.strictEqual( + activity.actor['oae:profilePath'], + '/user/camtest/' + AuthzUtil.getResourceFromId(jack.user.id).resourceId + ); + assert.strictEqual(activity.object['oae:id'], link.id); + assert.strictEqual( + activity.object['oae:profilePath'], + '/content/camtest/' + AuthzUtil.getResourceFromId(link.id).resourceId + ); + assert.strictEqual(activity.target['oae:id'], jane.user.id); + assert.strictEqual( + activity.target['oae:profilePath'], + '/user/camtest/' + AuthzUtil.getResourceFromId(jane.user.id).resourceId ); + return callback(); } ); - } - ); - } - ); + }); + } + ); + }); } ); }); @@ -3783,48 +3191,38 @@ describe('Content Activity', () => { assert.ok(!err); // Share google link with jane and branden - RestAPI.Content.shareContent( - jack.restContext, - googleLink.id, - [jane.user.id, branden.user.id], - err => { + RestAPI.Content.shareContent(jack.restContext, googleLink.id, [jane.user.id, branden.user.id], err => { + assert.ok(!err); + + // Share Yahoo link with jane only + RestAPI.Content.shareContent(jack.restContext, yahooLink.id, [jane.user.id], err => { assert.ok(!err); - // Share Yahoo link with jane only - RestAPI.Content.shareContent( + // Verify that the share activities aggregated in both pivot points + ActivityTestsUtil.collectAndGetActivityStream( jack.restContext, - yahooLink.id, - [jane.user.id], - err => { + null, + null, + (err, activityStream) => { assert.ok(!err); + assert.ok(activityStream); + assert.strictEqual(activityStream.items.length, 3); - // Verify that the share activities aggregated in both pivot points - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok(activityStream); - assert.strictEqual(activityStream.items.length, 3); - - // 1. actor+target should have jack+(google,yahoo)+jane, and it would be most recent - let activity = activityStream.items[0]; - assert.ok(activity.object['oae:collection']); - assert.strictEqual(activity.object['oae:collection'].length, 2); + // 1. actor+target should have jack+(google,yahoo)+jane, and it would be most recent + let activity = activityStream.items[0]; + assert.ok(activity.object['oae:collection']); + assert.strictEqual(activity.object['oae:collection'].length, 2); - // 2. actor+object aggregate should have: jack+google+(jane,branden) - activity = activityStream.items[1]; - assert.ok(activity.target['oae:collection']); - assert.strictEqual(activity.target['oae:collection'].length, 2); + // 2. actor+object aggregate should have: jack+google+(jane,branden) + activity = activityStream.items[1]; + assert.ok(activity.target['oae:collection']); + assert.strictEqual(activity.target['oae:collection'].length, 2); - return callback(); - } - ); + return callback(); } ); - } - ); + }); + }); } ); } @@ -3866,68 +3264,48 @@ describe('Content Activity', () => { assert.ok(!err); // Share google link with jane - RestAPI.Content.shareContent( - jack.restContext, - googleLink.id, - [jane.user.id], - err => { + RestAPI.Content.shareContent(jack.restContext, googleLink.id, [jane.user.id], err => { + assert.ok(!err); + + // Perform a collection to activate some aggregates ahead of time + ActivityTestsUtil.collectAndGetActivityStream(jack.restContext, null, null, (err, activityStream) => { assert.ok(!err); - // Perform a collection to activate some aggregates ahead of time - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { + // Share google now with branden, should aggregate with the previous + RestAPI.Content.shareContent(jack.restContext, googleLink.id, [branden.user.id], err => { + assert.ok(!err); + + // Share Yahoo link with jane only + RestAPI.Content.shareContent(jack.restContext, yahooLink.id, [jane.user.id], err => { assert.ok(!err); - // Share google now with branden, should aggregate with the previous - RestAPI.Content.shareContent( + // Verify that the share activities aggregated in both pivot points + ActivityTestsUtil.collectAndGetActivityStream( jack.restContext, - googleLink.id, - [branden.user.id], - err => { - assert.ok(!err); - - // Share Yahoo link with jane only - RestAPI.Content.shareContent( - jack.restContext, - yahooLink.id, - [jane.user.id], - err => { - assert.ok(!err); - - // Verify that the share activities aggregated in both pivot points - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok(activityStream); - assert.strictEqual(activityStream.items.length, 3); + null, + null, + (err, activityStream) => { + assert.ok(!err); + assert.ok(activityStream); + assert.strictEqual(activityStream.items.length, 3); - // 1. actor+target should have jack+(google,yahoo)+jane, and it would be most recent - let activity = activityStream.items[0]; - assert.ok(activity.object['oae:collection']); - assert.strictEqual(activity.object['oae:collection'].length, 2); + // 1. actor+target should have jack+(google,yahoo)+jane, and it would be most recent + let activity = activityStream.items[0]; + assert.ok(activity.object['oae:collection']); + assert.strictEqual(activity.object['oae:collection'].length, 2); - // 2. actor+object aggregate should have: jack+google+(jane,branden) - activity = activityStream.items[1]; - assert.ok(activity.target['oae:collection']); - assert.strictEqual(activity.target['oae:collection'].length, 2); + // 2. actor+object aggregate should have: jack+google+(jane,branden) + activity = activityStream.items[1]; + assert.ok(activity.target['oae:collection']); + assert.strictEqual(activity.target['oae:collection'].length, 2); - return callback(); - } - ); - } - ); + return callback(); } ); - } - ); - } - ); + }); + }); + }); + }); } ); } @@ -3969,58 +3347,43 @@ describe('Content Activity', () => { assert.ok(!err); // Share google link with jane and branden - RestAPI.Content.shareContent( - jack.restContext, - googleLink.id, - [jane.user.id, branden.user.id], - err => { - assert.ok(!err); + RestAPI.Content.shareContent(jack.restContext, googleLink.id, [jane.user.id, branden.user.id], err => { + assert.ok(!err); - // Perform a collection to activate some aggregates ahead of time - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); + // Perform a collection to activate some aggregates ahead of time + ActivityTestsUtil.collectAndGetActivityStream(jack.restContext, null, null, (err, activityStream) => { + assert.ok(!err); - // Share Yahoo link with jane only - RestAPI.Content.shareContent( - jack.restContext, - yahooLink.id, - [jane.user.id], - err => { - assert.ok(!err); + // Share Yahoo link with jane only + RestAPI.Content.shareContent(jack.restContext, yahooLink.id, [jane.user.id], err => { + assert.ok(!err); - // Verify that the share activities aggregated in both pivot points - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok(activityStream); - assert.strictEqual(activityStream.items.length, 3); + // Verify that the share activities aggregated in both pivot points + ActivityTestsUtil.collectAndGetActivityStream( + jack.restContext, + null, + null, + (err, activityStream) => { + assert.ok(!err); + assert.ok(activityStream); + assert.strictEqual(activityStream.items.length, 3); - // 1. actor+target should have jack+(google,yahoo)+jane, and it would be most recent - let activity = activityStream.items[0]; - assert.ok(activity.object['oae:collection']); - assert.strictEqual(activity.object['oae:collection'].length, 2); + // 1. actor+target should have jack+(google,yahoo)+jane, and it would be most recent + let activity = activityStream.items[0]; + assert.ok(activity.object['oae:collection']); + assert.strictEqual(activity.object['oae:collection'].length, 2); - // 2. actor+object aggregate should have: jack+google+(jane,branden) - activity = activityStream.items[1]; - assert.ok(activity.target['oae:collection']); - assert.strictEqual(activity.target['oae:collection'].length, 2); + // 2. actor+object aggregate should have: jack+google+(jane,branden) + activity = activityStream.items[1]; + assert.ok(activity.target['oae:collection']); + assert.strictEqual(activity.target['oae:collection'].length, 2); - return callback(); - } - ); - } - ); - } - ); - } - ); + return callback(); + } + ); + }); + }); + }); } ); } @@ -4062,84 +3425,58 @@ describe('Content Activity', () => { assert.ok(!err); // Share google link with jane - RestAPI.Content.shareContent( - jack.restContext, - googleLink.id, - [jane.user.id], - err => { + RestAPI.Content.shareContent(jack.restContext, googleLink.id, [jane.user.id], err => { + assert.ok(!err); + + // Perform a collection to activate some aggregates ahead of time + ActivityTestsUtil.collectAndGetActivityStream(jack.restContext, null, null, (err, activityStream) => { assert.ok(!err); - // Perform a collection to activate some aggregates ahead of time - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); + // Share google now with branden, should aggregate with the previous + RestAPI.Content.shareContent(jack.restContext, googleLink.id, [branden.user.id], err => { + assert.ok(!err); - // Share google now with branden, should aggregate with the previous - RestAPI.Content.shareContent( - jack.restContext, - googleLink.id, - [branden.user.id], - err => { + // Perform a collection to activate some aggregates ahead of time + ActivityTestsUtil.collectAndGetActivityStream( + jack.restContext, + null, + null, + (err, activityStream) => { + assert.ok(!err); + + // Share Yahoo link with jane only + RestAPI.Content.shareContent(jack.restContext, yahooLink.id, [jane.user.id], err => { assert.ok(!err); - // Perform a collection to activate some aggregates ahead of time + // Verify that the share activities aggregated in both pivot points ActivityTestsUtil.collectAndGetActivityStream( jack.restContext, null, null, (err, activityStream) => { assert.ok(!err); + assert.ok(activityStream); + assert.strictEqual(activityStream.items.length, 3); - // Share Yahoo link with jane only - RestAPI.Content.shareContent( - jack.restContext, - yahooLink.id, - [jane.user.id], - err => { - assert.ok(!err); - - // Verify that the share activities aggregated in both pivot points - ActivityTestsUtil.collectAndGetActivityStream( - jack.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); - assert.ok(activityStream); - assert.strictEqual(activityStream.items.length, 3); - - // 1. actor+target should have jack+(google,yahoo)+jane, and it would be most recent - let activity = activityStream.items[0]; - assert.ok(activity.object['oae:collection']); - assert.strictEqual( - activity.object['oae:collection'].length, - 2 - ); + // 1. actor+target should have jack+(google,yahoo)+jane, and it would be most recent + let activity = activityStream.items[0]; + assert.ok(activity.object['oae:collection']); + assert.strictEqual(activity.object['oae:collection'].length, 2); - // 2. actor+object aggregate should have: jack+google+(jane,branden) - activity = activityStream.items[1]; - assert.ok(activity.target['oae:collection']); - assert.strictEqual( - activity.target['oae:collection'].length, - 2 - ); + // 2. actor+object aggregate should have: jack+google+(jane,branden) + activity = activityStream.items[1]; + assert.ok(activity.target['oae:collection']); + assert.strictEqual(activity.target['oae:collection'].length, 2); - return callback(); - } - ); - } - ); + return callback(); } ); - } - ); - } - ); - } - ); + }); + } + ); + }); + }); + }); } ); } @@ -4154,107 +3491,92 @@ describe('Content Activity', () => { * scrubbed. */ it('verify content-comment email and privacy', callback => { - TestsUtil.generateTestUsers( - camAdminRestContext, - 3, - (err, users, mrvisser, simong, nicolaas) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 3, (err, users, mrvisser, simong, nicolaas) => { + assert.ok(!err); - const simongUpdate = { - visibility: 'private', - publicAlias: 'swappedFromPublicAlias' - }; - PrincipalsTestUtil.assertUpdateUserSucceeds( - simong.restContext, - simong.user.id, - simongUpdate, - () => { - RestAPI.Content.createLink( - mrvisser.restContext, - 'Google', - 'Google', - 'public', - 'http://www.google.ca', - [], - [], - [], - (err, link) => { - assert.ok(!err); + const simongUpdate = { + visibility: 'private', + publicAlias: 'swappedFromPublicAlias' + }; + PrincipalsTestUtil.assertUpdateUserSucceeds(simong.restContext, simong.user.id, simongUpdate, () => { + RestAPI.Content.createLink( + mrvisser.restContext, + 'Google', + 'Google', + 'public', + 'http://www.google.ca', + [], + [], + [], + (err, link) => { + assert.ok(!err); - RestAPI.Content.createComment( - simong.restContext, - link.id, - '\n\nWould click again', - null, - (err, simongComment) => { - assert.ok(!err); + RestAPI.Content.createComment( + simong.restContext, + link.id, + '\n\nWould click again', + null, + (err, simongComment) => { + assert.ok(!err); - EmailTestsUtil.collectAndFetchAllEmails(messages => { - // There should be exactly one message, the one sent to mrvisser (manager of content item receives content-comment notification) - assert.strictEqual(messages.length, 1); + EmailTestsUtil.collectAndFetchAllEmails(messages => { + // There should be exactly one message, the one sent to mrvisser (manager of content item receives content-comment notification) + assert.strictEqual(messages.length, 1); - const stringEmail = JSON.stringify(messages[0], null, 2); - const message = messages[0]; + const stringEmail = JSON.stringify(messages[0], null, 2); + const message = messages[0]; - // Sanity check that the message is to mrvisser - assert.strictEqual(message.to[0].address, mrvisser.user.email); + // Sanity check that the message is to mrvisser + assert.strictEqual(message.to[0].address, mrvisser.user.email); - // Ensure that the subject of the email contains the poster's name - assert.notStrictEqual( - message.subject.indexOf('swappedFromPublicAlias'), - -1 - ); + // Ensure that the subject of the email contains the poster's name + assert.notStrictEqual(message.subject.indexOf('swappedFromPublicAlias'), -1); - // Ensure some data expected to be in the email is there - assert.notStrictEqual(stringEmail.indexOf(link.profilePath), -1); - assert.notStrictEqual(stringEmail.indexOf(link.displayName), -1); + // Ensure some data expected to be in the email is there + assert.notStrictEqual(stringEmail.indexOf(link.profilePath), -1); + assert.notStrictEqual(stringEmail.indexOf(link.displayName), -1); - // Ensure simong's private info is *nowhere* to be found - assert.strictEqual(stringEmail.indexOf(simong.user.displayName), -1); - assert.strictEqual(stringEmail.indexOf(simong.user.email), -1); - assert.strictEqual(stringEmail.indexOf(simong.user.locale), -1); + // Ensure simong's private info is *nowhere* to be found + assert.strictEqual(stringEmail.indexOf(simong.user.displayName), -1); + assert.strictEqual(stringEmail.indexOf(simong.user.email), -1); + assert.strictEqual(stringEmail.indexOf(simong.user.locale), -1); - // The message probably contains the public alias, though - assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); + // The message probably contains the public alias, though + assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); - // The message should have escaped the HTML content in the original message - assert.strictEqual(stringEmail.indexOf(''), -1); + // The message should have escaped the HTML content in the original message + assert.strictEqual(stringEmail.indexOf(''), -1); - // The new line characters should've been converted into paragraphs - assert.notStrictEqual(stringEmail.indexOf('Would click again

'), -1); + // The new line characters should've been converted into paragraphs + assert.notStrictEqual(stringEmail.indexOf('Would click again

'), -1); - // Post a comment as nicolaas and ensure the recent commenter, simong receives an email about it - RestAPI.Content.createComment( - nicolaas.restContext, - link.id, - 'It 404d', - null, - (err, nicolaasComment) => { - assert.ok(!err); + // Post a comment as nicolaas and ensure the recent commenter, simong receives an email about it + RestAPI.Content.createComment( + nicolaas.restContext, + link.id, + 'It 404d', + null, + (err, nicolaasComment) => { + assert.ok(!err); - EmailTestsUtil.collectAndFetchAllEmails(emails => { - // There should be 2 emails this time, one to the manager and one to the recent commenter, simong - assert.strictEqual(emails.length, 2); + EmailTestsUtil.collectAndFetchAllEmails(emails => { + // There should be 2 emails this time, one to the manager and one to the recent commenter, simong + assert.strictEqual(emails.length, 2); - const emailAddresses = [ - emails[0].to[0].address, - emails[1].to[0].address - ]; - assert.ok(_.contains(emailAddresses, simong.user.email)); - assert.ok(_.contains(emailAddresses, mrvisser.user.email)); - return callback(); - }); - } - ); - }); - } - ); + const emailAddresses = [emails[0].to[0].address, emails[1].to[0].address]; + assert.ok(_.contains(emailAddresses, simong.user.email)); + assert.ok(_.contains(emailAddresses, mrvisser.user.email)); + return callback(); + }); + } + ); + }); } ); } ); - } - ); + }); + }); }); /** @@ -4270,53 +3592,48 @@ describe('Content Activity', () => { visibility: 'private', publicAlias: 'swappedFromPublicAlias' }; - PrincipalsTestUtil.assertUpdateUserSucceeds( - simong.restContext, - simong.user.id, - simongUpdate, - () => { - // Create the link, sharing it with mrvisser during the creation step. We will ensure he gets an email about it - RestAPI.Content.createLink( - simong.restContext, - 'Google', - 'Google', - 'public', - 'http://www.google.ca', - [], - [mrvisser.user.id], - [], - (err, link) => { - assert.ok(!err); + PrincipalsTestUtil.assertUpdateUserSucceeds(simong.restContext, simong.user.id, simongUpdate, () => { + // Create the link, sharing it with mrvisser during the creation step. We will ensure he gets an email about it + RestAPI.Content.createLink( + simong.restContext, + 'Google', + 'Google', + 'public', + 'http://www.google.ca', + [], + [mrvisser.user.id], + [], + (err, link) => { + assert.ok(!err); - // Mrvisser should get an email, with simong's information scrubbed - EmailTestsUtil.collectAndFetchAllEmails(messages => { - // There should be exactly one message, the one sent to mrvisser - assert.strictEqual(messages.length, 1); + // Mrvisser should get an email, with simong's information scrubbed + EmailTestsUtil.collectAndFetchAllEmails(messages => { + // There should be exactly one message, the one sent to mrvisser + assert.strictEqual(messages.length, 1); - const stringEmail = JSON.stringify(messages[0]); - const message = messages[0]; + const stringEmail = JSON.stringify(messages[0]); + const message = messages[0]; - // Sanity check that the message is to mrvisser - assert.strictEqual(message.to[0].address, mrvisser.user.email); + // Sanity check that the message is to mrvisser + assert.strictEqual(message.to[0].address, mrvisser.user.email); - // Ensure some data expected to be in the email is there - assert.notStrictEqual(stringEmail.indexOf(link.profilePath), -1); - assert.notStrictEqual(stringEmail.indexOf(link.displayName), -1); + // Ensure some data expected to be in the email is there + assert.notStrictEqual(stringEmail.indexOf(link.profilePath), -1); + assert.notStrictEqual(stringEmail.indexOf(link.displayName), -1); - // Ensure simong's private info is *nowhere* to be found - assert.strictEqual(stringEmail.indexOf(simong.user.displayName), -1); - assert.strictEqual(stringEmail.indexOf(simong.user.email), -1); - assert.strictEqual(stringEmail.indexOf(simong.user.locale), -1); + // Ensure simong's private info is *nowhere* to be found + assert.strictEqual(stringEmail.indexOf(simong.user.displayName), -1); + assert.strictEqual(stringEmail.indexOf(simong.user.email), -1); + assert.strictEqual(stringEmail.indexOf(simong.user.locale), -1); - // The message probably contains the public alias, though - assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); + // The message probably contains the public alias, though + assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); - return callback(); - }); - } - ); - } - ); + return callback(); + }); + } + ); + }); }); }); @@ -4333,64 +3650,54 @@ describe('Content Activity', () => { visibility: 'private', publicAlias: 'swappedFromPublicAlias' }; - PrincipalsTestUtil.assertUpdateUserSucceeds( - simong.restContext, - simong.user.id, - simongUpdate, - () => { - // Create the link, then share it with mrvisser. We will ensure that mrvisser gets the email about the share - RestAPI.Content.createLink( - simong.restContext, - 'Google', - 'Google', - 'public', - 'http://www.google.ca', - [], - [], - [], - (err, link) => { - assert.ok(!err); + PrincipalsTestUtil.assertUpdateUserSucceeds(simong.restContext, simong.user.id, simongUpdate, () => { + // Create the link, then share it with mrvisser. We will ensure that mrvisser gets the email about the share + RestAPI.Content.createLink( + simong.restContext, + 'Google', + 'Google', + 'public', + 'http://www.google.ca', + [], + [], + [], + (err, link) => { + assert.ok(!err); - // Collect the createLink activity - EmailTestsUtil.collectAndFetchAllEmails(messages => { - RestAPI.Content.shareContent( - simong.restContext, - link.id, - [mrvisser.user.id], - err => { - assert.ok(!err); + // Collect the createLink activity + EmailTestsUtil.collectAndFetchAllEmails(messages => { + RestAPI.Content.shareContent(simong.restContext, link.id, [mrvisser.user.id], err => { + assert.ok(!err); - // Mrvisser should get an email, with simong's information scrubbed - EmailTestsUtil.collectAndFetchAllEmails(messages => { - // There should be exactly one message, the one sent to mrvisser - assert.strictEqual(messages.length, 1); + // Mrvisser should get an email, with simong's information scrubbed + EmailTestsUtil.collectAndFetchAllEmails(messages => { + // There should be exactly one message, the one sent to mrvisser + assert.strictEqual(messages.length, 1); - const stringEmail = JSON.stringify(messages[0]); - const message = messages[0]; + const stringEmail = JSON.stringify(messages[0]); + const message = messages[0]; - // Sanity check that the message is to mrvisser - assert.strictEqual(message.to[0].address, mrvisser.user.email); + // Sanity check that the message is to mrvisser + assert.strictEqual(message.to[0].address, mrvisser.user.email); - // Ensure some data expected to be in the email is there - assert.notStrictEqual(stringEmail.indexOf(link.profilePath), -1); - assert.notStrictEqual(stringEmail.indexOf(link.displayName), -1); + // Ensure some data expected to be in the email is there + assert.notStrictEqual(stringEmail.indexOf(link.profilePath), -1); + assert.notStrictEqual(stringEmail.indexOf(link.displayName), -1); - // Ensure simong's private info is *nowhere* to be found - assert.strictEqual(stringEmail.indexOf(simong.user.displayName), -1); - assert.strictEqual(stringEmail.indexOf(simong.user.email), -1); - assert.strictEqual(stringEmail.indexOf(simong.user.locale), -1); + // Ensure simong's private info is *nowhere* to be found + assert.strictEqual(stringEmail.indexOf(simong.user.displayName), -1); + assert.strictEqual(stringEmail.indexOf(simong.user.email), -1); + assert.strictEqual(stringEmail.indexOf(simong.user.locale), -1); - // The message probably contains the public alias, though - assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); - return callback(); - }); - } - ); + // The message probably contains the public alias, though + assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); + return callback(); + }); }); - } - ); - } - ); + }); + } + ); + }); }); }); }); diff --git a/packages/oae-content/tests/test-backends.js b/packages/oae-content/tests/test-backends.js index 2cc5a1268d..2c06b83689 100644 --- a/packages/oae-content/tests/test-backends.js +++ b/packages/oae-content/tests/test-backends.js @@ -13,12 +13,12 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const BackendsUtil = require('oae-content/lib/backends/util'); -const LocalBackend = require('oae-content/lib/backends/local'); -const RemoteBackend = require('oae-content/lib/backends/remote'); +import * as BackendsUtil from 'oae-content/lib/backends/util'; +import * as LocalBackend from 'oae-content/lib/backends/local'; +import * as RemoteBackend from 'oae-content/lib/backends/remote'; describe('Content Backends', () => { describe('Util', () => { @@ -34,25 +34,14 @@ describe('Content Backends', () => { const uri = BackendsUtil.generateUri(file, options); const result = uri.split('/'); - assert.strictEqual( - result[0], - 'c', - 'The first level of a URI should be the resource type (or unspecified.)' - ); + assert.strictEqual(result[0], 'c', 'The first level of a URI should be the resource type (or unspecified.)'); assert.strictEqual( result[1], 'camtest', 'The second level of a URI should be the tenant alias (or unspecified.)' ); - assert.strictEqual( - result[result.length - 1], - 'testfile.png', - 'The last level of the URI should be the filename' - ); - assert.ok( - result.length > 3, - 'A URI should have some kind of hashing in it which generated more than 3 levels' - ); + assert.strictEqual(result[result.length - 1], 'testfile.png', 'The last level of the URI should be the filename'); + assert.ok(result.length > 3, 'A URI should have some kind of hashing in it which generated more than 3 levels'); _.each(result, part => { assert.ok(part.length > 0, 'Each part of the URI should be non-empty.'); }); @@ -69,11 +58,7 @@ describe('Content Backends', () => { const uri = BackendsUtil.generateUri(file, options); const result = uri.split('/'); - assert.strictEqual( - result[0], - 'u', - 'The first level of a URI should be the resource type (or unspecified.)' - ); + assert.strictEqual(result[0], 'u', 'The first level of a URI should be the resource type (or unspecified.)'); assert.strictEqual( result[1], 'camtest', @@ -84,11 +69,7 @@ describe('Content Backends', () => { 'profilepictures', 'The second to last level of the URI should be the prefix (if it contains no slashes.)' ); - assert.strictEqual( - result[result.length - 1], - 'testfile.png', - 'The last level of the URI should be the filename' - ); + assert.strictEqual(result[result.length - 1], 'testfile.png', 'The last level of the URI should be the filename'); assert.ok( result.length > 4, 'A URI should have some kind of hashing in it which generated more than 4 levels if a prefix is specified' @@ -116,15 +97,8 @@ describe('Content Backends', () => { 'unspecified', 'The second level of a URI should be the tenant alias (or unspecified.)' ); - assert.strictEqual( - result[result.length - 1], - 'testfile.png', - 'The last level of the URI should be the filename' - ); - assert.ok( - result.length > 3, - 'A URI should have some kind of hashing in it which generated more than 3 levels' - ); + assert.strictEqual(result[result.length - 1], 'testfile.png', 'The last level of the URI should be the filename'); + assert.ok(result.length > 3, 'A URI should have some kind of hashing in it which generated more than 3 levels'); _.each(result, part => { assert.ok(part.length > 0, 'Each part of the URI should be non-empty.'); }); @@ -140,25 +114,14 @@ describe('Content Backends', () => { const uri = BackendsUtil.generateUri(file, options); const result = uri.split('/'); - assert.strictEqual( - result[0], - 'c', - 'The first level of a URI should be the resource type (or unspecified.)' - ); + assert.strictEqual(result[0], 'c', 'The first level of a URI should be the resource type (or unspecified.)'); assert.strictEqual( result[1], 'camtest', 'The second level of a URI should be the tenant alias (or unspecified.)' ); - assert.strictEqual( - result[result.length - 1], - 'testfile.png', - 'The last level of the URI should be the filename' - ); - assert.ok( - result.length > 3, - 'A URI should have some kind of hashing in it which generated more than 3 levels' - ); + assert.strictEqual(result[result.length - 1], 'testfile.png', 'The last level of the URI should be the filename'); + assert.ok(result.length > 3, 'A URI should have some kind of hashing in it which generated more than 3 levels'); _.each(result, part => { assert.ok(part.length > 0, 'Each part of the URI should be non-empty.'); }); diff --git a/packages/oae-content/tests/test-collabdoc.js b/packages/oae-content/tests/test-collabdoc.js index a0eaffeec7..430a140e0a 100644 --- a/packages/oae-content/tests/test-collabdoc.js +++ b/packages/oae-content/tests/test-collabdoc.js @@ -13,19 +13,17 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const url = require('url'); -const util = require('util'); -const ShortId = require('shortid'); -const _ = require('underscore'); - -const ActivityTestUtil = require('oae-activity/lib/test/util'); -const RestAPI = require('oae-rest'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); - -const ContentTestUtil = require('oae-content/lib/test/util'); -const Etherpad = require('oae-content/lib/internal/etherpad'); +import assert from 'assert'; +import url from 'url'; +import util from 'util'; +import ShortId from 'shortid'; +import _ from 'underscore'; + +import * as RestAPI from 'oae-rest'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests'; +import * as ContentTestUtil from 'oae-content/lib/test/util'; +import * as Etherpad from 'oae-content/lib/internal/etherpad'; describe('Collaborative documents', () => { // Rest context that can be used every time we need to make a request as an anonymous user diff --git a/packages/oae-content/tests/test-collabsheet.js b/packages/oae-content/tests/test-collabsheet.js index 13f5f638d4..58f4ac3126 100644 --- a/packages/oae-content/tests/test-collabsheet.js +++ b/packages/oae-content/tests/test-collabsheet.js @@ -13,14 +13,13 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as ContentTestUtil from 'oae-content/lib/test/util'; +import * as Ethercalc from 'oae-content/lib/internal/ethercalc'; -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); - -const ContentTestUtil = require('oae-content/lib/test/util'); -const Ethercalc = require('oae-content/lib/internal/ethercalc'); +import _ from 'underscore'; describe('Collaborative spreadsheets', function() { // Rest context that can be used every time we need to make a request as an anonymous user diff --git a/packages/oae-content/tests/test-content.js b/packages/oae-content/tests/test-content.js index 8ab15fea5e..4b92ee60a5 100644 --- a/packages/oae-content/tests/test-content.js +++ b/packages/oae-content/tests/test-content.js @@ -13,30 +13,29 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const url = require('url'); -const util = require('util'); -const temp = require('temp'); -const _ = require('underscore'); - -const AuthzAPI = require('oae-authz'); -const AuthzTestUtil = require('oae-authz/lib/test/util'); -const AuthzUtil = require('oae-authz/lib/util'); -const { Context } = require('oae-context'); -const PreviewConstants = require('oae-preview-processor/lib/constants'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const RestUtil = require('oae-rest/lib/util'); -const TenantsAPI = require('oae-tenants/lib/api'); -const TaskQueue = require('oae-util/lib/taskqueue'); -const TestsUtil = require('oae-tests'); - -const ContentAPI = require('oae-content'); -const ContentTestUtil = require('oae-content/lib/test/util'); -const ContentUtil = require('oae-content/lib/internal/util'); +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import url from 'url'; +import util from 'util'; +import temp from 'temp'; +import _ from 'underscore'; + +import * as AuthzAPI from 'oae-authz'; +import * as AuthzTestUtil from 'oae-authz/lib/test/util'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import { Context } from 'oae-context'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import { RestContext } from 'oae-rest/lib/model'; +import * as RestUtil from 'oae-rest/lib/util'; +import * as TenantsAPI from 'oae-tenants/lib/api'; +import * as TaskQueue from 'oae-util/lib/taskqueue'; +import * as TestsUtil from 'oae-tests'; +import * as ContentAPI from 'oae-content'; +import * as ContentTestUtil from 'oae-content/lib/test/util'; +import * as ContentUtil from 'oae-content/lib/internal/util'; const PUBLIC = 'public'; const PRIVATE = 'private'; diff --git a/packages/oae-content/tests/test-dao.js b/packages/oae-content/tests/test-dao.js index 715c7c37c7..6b71d75919 100644 --- a/packages/oae-content/tests/test-dao.js +++ b/packages/oae-content/tests/test-dao.js @@ -13,14 +13,14 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const _ = require('underscore'); +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import _ from 'underscore'; -const ContentDAO = require('oae-content/lib/internal/dao'); -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); +import * as ContentDAO from 'oae-content/lib/internal/dao'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; describe('Content DAO', () => { // Rest contexts that will be used for requests @@ -75,8 +75,8 @@ describe('Content DAO', () => { let foundLink = false; /*! - * Verifies that only the contentId is returned in the content row - */ + * Verifies that only the contentId is returned in the content row + */ const _onEach = function(contentRows, done) { // Ensure we only get the contentId of the content item _.each(contentRows, contentRow => { @@ -104,9 +104,9 @@ describe('Content DAO', () => { foundLink = false; /*! - * Verifies that only the contentId and displayName of the content rows are returned, and that they are - * accurate. - */ + * Verifies that only the contentId and displayName of the content rows are returned, and that they are + * accurate. + */ const _onEach = function(contentRows, done) { // Ensure we only get the contentId and displayName of the content item _.each(contentRows, contentRow => { @@ -165,36 +165,23 @@ describe('Content DAO', () => { assert.ok(!err); RestAPI.Content.updateFileBody(mrvisser.restContext, contentObj.id, getStream, err => { assert.ok(!err); - RestAPI.Content.updateFileBody( - mrvisser.restContext, - contentObj.id, - getStream, - err => { + RestAPI.Content.updateFileBody(mrvisser.restContext, contentObj.id, getStream, err => { + assert.ok(!err); + RestAPI.Content.updateFileBody(mrvisser.restContext, contentObj.id, getStream, err => { assert.ok(!err); - RestAPI.Content.updateFileBody( - mrvisser.restContext, - contentObj.id, - getStream, - err => { - assert.ok(!err); - - ContentDAO.Revisions.getAllRevisionsForContent( - [contentObj.id], - (err, data) => { - assert.ok(!err); - assert.ok(data[contentObj.id]); - assert.ok(data[contentObj.id].length, 5); - _.each(data[contentObj.id], revision => { - assert.strictEqual(revision.contentId, contentObj.id); - assert.strictEqual(revision.filename, 'apereo.jpg'); - }); - return callback(); - } - ); - } - ); - } - ); + + ContentDAO.Revisions.getAllRevisionsForContent([contentObj.id], (err, data) => { + assert.ok(!err); + assert.ok(data[contentObj.id]); + assert.ok(data[contentObj.id].length, 5); + _.each(data[contentObj.id], revision => { + assert.strictEqual(revision.contentId, contentObj.id); + assert.strictEqual(revision.filename, 'apereo.jpg'); + }); + return callback(); + }); + }); + }); }); }); } diff --git a/packages/oae-content/tests/test-library-search.js b/packages/oae-content/tests/test-library-search.js index d090aaf02e..afd7253586 100644 --- a/packages/oae-content/tests/test-library-search.js +++ b/packages/oae-content/tests/test-library-search.js @@ -13,12 +13,12 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const RestAPI = require('oae-rest'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); +import * as RestAPI from 'oae-rest'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests'; describe('Library Search', () => { // REST contexts we can use to do REST requests @@ -50,68 +50,62 @@ describe('Library Search', () => { [], (err, content) => { assert.ok(!err); - RestAPI.Content.createComment( - simon.restContext, - content.id, - 'abcdefghi', - null, - (err, comment) => { - assert.ok(!err); + RestAPI.Content.createComment(simon.restContext, content.id, 'abcdefghi', null, (err, comment) => { + assert.ok(!err); - // Keep in mind that messages are analyzed with an edgengram analyzer with its - // minimum set to 5. As tokenisation is letter based, we can't really generate - // a test string or use an md5 hash as those are probably not going to contain - // substrings of 5 characters - SearchTestsUtil.searchAll( - simon.restContext, - 'content-library', - [simon.user.id], - { q: 'abcdefghijklmn' }, - (err, results) => { - assert.ok(!err); - assert.ok(_.find(results.results, { id: content.id })); - - // Create a discussion with a message on it - RestAPI.Discussions.createDiscussion( - simon.restContext, - 'A talk', - 'about the moon', - 'public', - [], - [], - (err, discussion) => { - assert.ok(!err); - RestAPI.Discussions.createMessage( - simon.restContext, - discussion.id, - 'stuvwxyz', - null, - (err, message) => { - assert.ok(!err); - - // Keep in mind that messages are analyzed with an edgengram analyzer with its - // minimum set to 5. As tokenisation is letter based, we can't really generate - // a test string or use an md5 hash as those are probably not going to contain - // substrings of 5 characters - SearchTestsUtil.searchAll( - simon.restContext, - 'discussion-library', - [simon.user.id], - { q: 'stuvwxyz' }, - (err, results) => { - assert.ok(!err); - assert.ok(_.find(results.results, { id: discussion.id })); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + // Keep in mind that messages are analyzed with an edgengram analyzer with its + // minimum set to 5. As tokenisation is letter based, we can't really generate + // a test string or use an md5 hash as those are probably not going to contain + // substrings of 5 characters + SearchTestsUtil.searchAll( + simon.restContext, + 'content-library', + [simon.user.id], + { q: 'abcdefghijklmn' }, + (err, results) => { + assert.ok(!err); + assert.ok(_.find(results.results, { id: content.id })); + + // Create a discussion with a message on it + RestAPI.Discussions.createDiscussion( + simon.restContext, + 'A talk', + 'about the moon', + 'public', + [], + [], + (err, discussion) => { + assert.ok(!err); + RestAPI.Discussions.createMessage( + simon.restContext, + discussion.id, + 'stuvwxyz', + null, + (err, message) => { + assert.ok(!err); + + // Keep in mind that messages are analyzed with an edgengram analyzer with its + // minimum set to 5. As tokenisation is letter based, we can't really generate + // a test string or use an md5 hash as those are probably not going to contain + // substrings of 5 characters + SearchTestsUtil.searchAll( + simon.restContext, + 'discussion-library', + [simon.user.id], + { q: 'stuvwxyz' }, + (err, results) => { + assert.ok(!err); + assert.ok(_.find(results.results, { id: discussion.id })); + return callback(); + } + ); + } + ); + } + ); + } + ); + }); } ); }); @@ -122,29 +116,17 @@ describe('Library Search', () => { * Test that verifies only valid principal ids return results */ it('verify the principal id gets validated', callback => { - SearchTestsUtil.searchAll( - camAdminRestContext, - 'content-library', - [''], - null, - (err, results) => { + SearchTestsUtil.searchAll(camAdminRestContext, 'content-library', [''], null, (err, results) => { + assert.strictEqual(err.code, 400); + assert.ok(!results); + + SearchTestsUtil.searchAll(camAdminRestContext, 'content-library', ['invalid-user-id'], null, (err, results) => { assert.strictEqual(err.code, 400); assert.ok(!results); - SearchTestsUtil.searchAll( - camAdminRestContext, - 'content-library', - ['invalid-user-id'], - null, - (err, results) => { - assert.strictEqual(err.code, 400); - assert.ok(!results); - - return callback(); - } - ); - } - ); + return callback(); + }); + }); }); /** diff --git a/packages/oae-content/tests/test-library.js b/packages/oae-content/tests/test-library.js index f902a3a631..5863b2afa3 100644 --- a/packages/oae-content/tests/test-library.js +++ b/packages/oae-content/tests/test-library.js @@ -13,15 +13,15 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const Cassandra = require('oae-util/lib/cassandra'); -const ConfigTestUtil = require('oae-config/lib/test/util'); -const LibraryAPI = require('oae-library'); -const RestAPI = require('oae-rest'); -const TenantsTestUtil = require('oae-tenants/lib/test/util'); -const TestsUtil = require('oae-tests'); +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as LibraryAPI from 'oae-library'; +import * as RestAPI from 'oae-rest'; +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; +import * as TestsUtil from 'oae-tests'; describe('Content Libraries', () => { let camAnonymousRestCtx = null; @@ -87,46 +87,29 @@ describe('Content Libraries', () => { items.push(contentObj.id); // Get the 2 most recent items. - RestAPI.Content.getLibrary( - nicolaas.restContext, - nicolaas.user.id, - null, - 2, - (err, data) => { - assert.ok(!err); - const library = data.results; - assert.strictEqual(library.length, 2); - assert.strictEqual(library[0].id, items[2]); - assert.strictEqual(library[1].id, items[1]); + RestAPI.Content.getLibrary(nicolaas.restContext, nicolaas.user.id, null, 2, (err, data) => { + assert.ok(!err); + const library = data.results; + assert.strictEqual(library.length, 2); + assert.strictEqual(library[0].id, items[2]); + assert.strictEqual(library[1].id, items[1]); - // Modify the oldest one. - RestAPI.Content.updateContent( - nicolaas.restContext, - items[0], - { description: 'lalila' }, - err => { - assert.ok(!err); + // Modify the oldest one. + RestAPI.Content.updateContent(nicolaas.restContext, items[0], { description: 'lalila' }, err => { + assert.ok(!err); - // When we retrieve the library the just modified one, should be on-top. - RestAPI.Content.getLibrary( - nicolaas.restContext, - nicolaas.user.id, - null, - 2, - (err, data) => { - assert.ok(!err); - const library = data.results; - assert.strictEqual(library.length, 2); - assert.strictEqual(library[0].id, items[0]); - assert.strictEqual(library[1].id, items[2]); + // When we retrieve the library the just modified one, should be on-top. + RestAPI.Content.getLibrary(nicolaas.restContext, nicolaas.user.id, null, 2, (err, data) => { + assert.ok(!err); + const library = data.results; + assert.strictEqual(library.length, 2); + assert.strictEqual(library[0].id, items[0]); + assert.strictEqual(library[1].id, items[2]); - callback(); - } - ); - } - ); - } - ); + callback(); + }); + }); + }); } ); } @@ -147,30 +130,18 @@ describe('Content Libraries', () => { RestAPI.Content.getLibrary(simon.restContext, ' ', null, null, (err, data) => { assert.strictEqual(err.code, 400); - RestAPI.Content.getLibrary( - simon.restContext, - 'invalid-user-id', - null, - null, - (err, data) => { - assert.strictEqual(err.code, 400); + RestAPI.Content.getLibrary(simon.restContext, 'invalid-user-id', null, null, (err, data) => { + assert.strictEqual(err.code, 400); - RestAPI.Content.getLibrary(simon.restContext, 'c:cam:bleh', null, null, (err, data) => { - assert.strictEqual(err.code, 400); + RestAPI.Content.getLibrary(simon.restContext, 'c:cam:bleh', null, null, (err, data) => { + assert.strictEqual(err.code, 400); - RestAPI.Content.getLibrary( - simon.restContext, - 'u:cam:bleh', - null, - null, - (err, data) => { - assert.strictEqual(err.code, 404); - callback(); - } - ); + RestAPI.Content.getLibrary(simon.restContext, 'u:cam:bleh', null, null, (err, data) => { + assert.strictEqual(err.code, 404); + callback(); }); - } - ); + }); + }); }); }); }); @@ -195,42 +166,27 @@ describe('Content Libraries', () => { (err, contentObj) => { assert.ok(!err); - RestAPI.Content.removeContentFromLibrary( - camAnonymousRestCtx, - simon.user.id, - contentObj.id, - err => { - assert.strictEqual(err.code, 401); + RestAPI.Content.removeContentFromLibrary(camAnonymousRestCtx, simon.user.id, contentObj.id, err => { + assert.strictEqual(err.code, 401); - RestAPI.Content.removeContentFromLibrary( - simon.restContext, - 'invalid-user-id', - contentObj.id, - err => { - assert.strictEqual(err.code, 400); + RestAPI.Content.removeContentFromLibrary(simon.restContext, 'invalid-user-id', contentObj.id, err => { + assert.strictEqual(err.code, 400); - RestAPI.Content.removeContentFromLibrary( - simon.restContext, - simon.user.id, - 'invalid-content-id', - err => { - assert.strictEqual(err.code, 400); + RestAPI.Content.removeContentFromLibrary(simon.restContext, simon.user.id, 'invalid-content-id', err => { + assert.strictEqual(err.code, 400); - RestAPI.Content.removeContentFromLibrary( - simon.restContext, - simon.user.id, - 'c:camtest:nonexisting', - err => { - assert.strictEqual(err.code, 404); - callback(); - } - ); - } - ); - } - ); - } - ); + RestAPI.Content.removeContentFromLibrary( + simon.restContext, + simon.user.id, + 'c:camtest:nonexisting', + err => { + assert.strictEqual(err.code, 404); + callback(); + } + ); + }); + }); + }); } ); }); @@ -259,18 +215,12 @@ describe('Content Libraries', () => { RestAPI.Content.deleteContent(nicolaas.restContext, contentObj.id, err => { assert.ok(!err); - RestAPI.Content.getLibrary( - nicolaas.restContext, - nicolaas.user.id, - null, - null, - (err, data) => { - assert.ok(!err); - const library = data.results; - assert.strictEqual(library.length, 0); - callback(); - } - ); + RestAPI.Content.getLibrary(nicolaas.restContext, nicolaas.user.id, null, null, (err, data) => { + assert.ok(!err); + const library = data.results; + assert.strictEqual(library.length, 0); + callback(); + }); }); } ); @@ -298,49 +248,27 @@ describe('Content Libraries', () => { (err, contentObj) => { assert.ok(!err); - RestAPI.Content.shareContent( - nicolaas.restContext, - contentObj.id, - [simon.user.id], - err => { + RestAPI.Content.shareContent(nicolaas.restContext, contentObj.id, [simon.user.id], err => { + assert.ok(!err); + + // Sanity check that Simon has the item + RestAPI.Content.getLibrary(simon.restContext, simon.user.id, null, null, (err, data) => { assert.ok(!err); + const library = data.results; + assert.strictEqual(library.length, 1); + assert.strictEqual(library[0].id, contentObj.id); - // Sanity check that Simon has the item - RestAPI.Content.getLibrary( - simon.restContext, - simon.user.id, - null, - null, - (err, data) => { + RestAPI.Content.removeContentFromLibrary(simon.restContext, simon.user.id, contentObj.id, err => { + assert.ok(!err); + RestAPI.Content.getLibrary(simon.restContext, simon.user.id, null, null, (err, data) => { assert.ok(!err); const library = data.results; - assert.strictEqual(library.length, 1); - assert.strictEqual(library[0].id, contentObj.id); - - RestAPI.Content.removeContentFromLibrary( - simon.restContext, - simon.user.id, - contentObj.id, - err => { - assert.ok(!err); - RestAPI.Content.getLibrary( - simon.restContext, - simon.user.id, - null, - null, - (err, data) => { - assert.ok(!err); - const library = data.results; - assert.strictEqual(library.length, 0); - callback(); - } - ); - } - ); - } - ); - } - ); + assert.strictEqual(library.length, 0); + callback(); + }); + }); + }); + }); } ); }); @@ -369,15 +297,10 @@ describe('Content Libraries', () => { // Nicolaas can't remove the content from his library // as he is the only manager for it. - RestAPI.Content.removeContentFromLibrary( - nicolaas.restContext, - nicolaas.user.id, - contentObj.id, - err => { - assert.strictEqual(err.code, 400); - callback(); - } - ); + RestAPI.Content.removeContentFromLibrary(nicolaas.restContext, nicolaas.user.id, contentObj.id, err => { + assert.strictEqual(err.code, 400); + callback(); + }); } ); }); @@ -423,49 +346,32 @@ describe('Content Libraries', () => { assert.ok(!err); // Sanity check that userB has the item in his library - RestAPI.Content.getLibrary( - userB.restContext, - userB.user.id, - null, - null, - (err, data) => { - assert.ok(!err); - const library = data.results; - assert.strictEqual(library.length, 1); - assert.strictEqual(library[0].id, contentObj.id); + RestAPI.Content.getLibrary(userB.restContext, userB.user.id, null, null, (err, data) => { + assert.ok(!err); + const library = data.results; + assert.strictEqual(library.length, 1); + assert.strictEqual(library[0].id, contentObj.id); - // Now make tenantA private. - ConfigTestUtil.updateConfigAndWait( - globalAdminRestContext, - tenantAliasA, - { 'oae-tenants/tenantprivacy/tenantprivate': true }, - err => { - assert.ok(!err); + // Now make tenantA private. + ConfigTestUtil.updateConfigAndWait( + globalAdminRestContext, + tenantAliasA, + { 'oae-tenants/tenantprivacy/tenantprivate': true }, + err => { + assert.ok(!err); - RestAPI.Content.removeContentFromLibrary( - userB.restContext, - userB.user.id, - contentObj.id, - err => { - assert.ok(!err); - RestAPI.Content.getLibrary( - userB.restContext, - userB.user.id, - null, - null, - (err, data) => { - assert.ok(!err); - const library = data.results; - assert.strictEqual(library.length, 0); - callback(); - } - ); - } - ); - } - ); - } - ); + RestAPI.Content.removeContentFromLibrary(userB.restContext, userB.user.id, contentObj.id, err => { + assert.ok(!err); + RestAPI.Content.getLibrary(userB.restContext, userB.user.id, null, null, (err, data) => { + assert.ok(!err); + const library = data.results; + assert.strictEqual(library.length, 0); + callback(); + }); + }); + } + ); + }); } ); }); @@ -496,29 +402,18 @@ describe('Content Libraries', () => { assert.ok(!err); // This should fail as Simon can't manage Nicolaas his library. - RestAPI.Content.removeContentFromLibrary( - simon.restContext, - nicolaas.user.id, - contentObj.id, - err => { - assert.strictEqual(err.code, 401); + RestAPI.Content.removeContentFromLibrary(simon.restContext, nicolaas.user.id, contentObj.id, err => { + assert.strictEqual(err.code, 401); - // Sanity check Nicolaas his library to ensure nothing got removed. - RestAPI.Content.getLibrary( - nicolaas.restContext, - nicolaas.user.id, - null, - null, - (err, data) => { - assert.ok(!err); - const library = data.results; - assert.strictEqual(library.length, 1); - assert.strictEqual(library[0].id, contentObj.id); - callback(); - } - ); - } - ); + // Sanity check Nicolaas his library to ensure nothing got removed. + RestAPI.Content.getLibrary(nicolaas.restContext, nicolaas.user.id, null, null, (err, data) => { + assert.ok(!err); + const library = data.results; + assert.strictEqual(library.length, 1); + assert.strictEqual(library[0].id, contentObj.id); + callback(); + }); + }); } ); }); @@ -559,42 +454,36 @@ describe('Content Libraries', () => { assert.ok(!err); // Sanity check it's there. - RestAPI.Content.getLibrary( - nicolaas.restContext, - grandParent.group.id, - null, - null, - (err, data) => { - assert.ok(!err); - const library = data.results; - assert.strictEqual(library.length, 1); - assert.strictEqual(library[0].id, contentObj.id); + RestAPI.Content.getLibrary(nicolaas.restContext, grandParent.group.id, null, null, (err, data) => { + assert.ok(!err); + const library = data.results; + assert.strictEqual(library.length, 1); + assert.strictEqual(library[0].id, contentObj.id); - // Simon decides the content isn't all that great and removes it. - RestAPI.Content.removeContentFromLibrary( - simon.restContext, - grandParent.group.id, - contentObj.id, - err => { - assert.ok(!err); + // Simon decides the content isn't all that great and removes it. + RestAPI.Content.removeContentFromLibrary( + simon.restContext, + grandParent.group.id, + contentObj.id, + err => { + assert.ok(!err); - // Sanity check that it's gone. - RestAPI.Content.getLibrary( - nicolaas.restContext, - grandParent.group.id, - null, - null, - (err, data) => { - assert.ok(!err); - const library = data.results; - assert.strictEqual(library.length, 0); - return callback(); - } - ); - } - ); - } - ); + // Sanity check that it's gone. + RestAPI.Content.getLibrary( + nicolaas.restContext, + grandParent.group.id, + null, + null, + (err, data) => { + assert.ok(!err); + const library = data.results; + assert.strictEqual(library.length, 0); + return callback(); + } + ); + } + ); + }); } ); }); @@ -630,6 +519,7 @@ describe('Content Libraries', () => { assert.strictEqual(err.code, 401); assert.ok(!items); } + callback(); }); }; @@ -649,125 +539,111 @@ describe('Content Libraries', () => { // Create a user with the proper visibility TestsUtil.generateTestUsers(restCtx, 1, (err, users) => { const { 0: user } = _.values(users); - RestAPI.User.updateUser( - user.restContext, - user.user.id, - { visibility: userVisibility }, - err => { - assert.ok(!err); - - // Fill up this user his library with 3 content items. - RestAPI.Content.createLink( - user.restContext, - 'name', - 'description', - 'private', - 'http://www.oaeproject.org', - null, - null, - [], - (err, privateContent) => { - assert.ok(!err); - RestAPI.Content.createLink( - user.restContext, - 'name', - 'description', - 'loggedin', - 'http://www.oaeproject.org', - null, - null, - [], - (err, loggedinContent) => { - assert.ok(!err); - RestAPI.Content.createLink( - user.restContext, - 'name', - 'description', - 'public', - 'http://www.oaeproject.org', - null, - null, - [], - (err, publicContent) => { - assert.ok(!err); - callback(user, privateContent, loggedinContent, publicContent); - } - ); - } - ); - } - ); - } - ); - }); - }; - - /** - * Creates a group with the supplied visibility and fill its library with 3 content items. - * - * @param {RestContext} restCtx The context with which to create the group and content - * @param {String} groupVisibility The visibility for the new group - * @param {Function} callback Standard callback function - * @param {Group} callback.group The created group - * @param {Content} callback.privateContent The private piece of content - * @param {Content} callback.loggedinContent The loggedin piece of content - * @param {Content} callback.publicContent The public piece of content - */ - const createGroupAndLibrary = function(restCtx, groupVisibility, callback) { - RestAPI.Group.createGroup( - restCtx, - 'displayName', - 'description', - groupVisibility, - 'no', - [], - [], - (err, group) => { + RestAPI.User.updateUser(user.restContext, user.user.id, { visibility: userVisibility }, err => { assert.ok(!err); - // Fill up the group library with 3 content items. + // Fill up this user his library with 3 content items. RestAPI.Content.createLink( - restCtx, + user.restContext, 'name', 'description', 'private', 'http://www.oaeproject.org', - [group.id], + null, null, [], (err, privateContent) => { assert.ok(!err); RestAPI.Content.createLink( - restCtx, + user.restContext, 'name', 'description', 'loggedin', 'http://www.oaeproject.org', - [group.id], + null, null, [], (err, loggedinContent) => { assert.ok(!err); RestAPI.Content.createLink( - restCtx, + user.restContext, 'name', 'description', 'public', 'http://www.oaeproject.org', - [group.id], + null, null, [], (err, publicContent) => { assert.ok(!err); - callback(group, privateContent, loggedinContent, publicContent); + callback(user, privateContent, loggedinContent, publicContent); } ); } ); } ); - } - ); + }); + }); + }; + + /** + * Creates a group with the supplied visibility and fill its library with 3 content items. + * + * @param {RestContext} restCtx The context with which to create the group and content + * @param {String} groupVisibility The visibility for the new group + * @param {Function} callback Standard callback function + * @param {Group} callback.group The created group + * @param {Content} callback.privateContent The private piece of content + * @param {Content} callback.loggedinContent The loggedin piece of content + * @param {Content} callback.publicContent The public piece of content + */ + const createGroupAndLibrary = function(restCtx, groupVisibility, callback) { + RestAPI.Group.createGroup(restCtx, 'displayName', 'description', groupVisibility, 'no', [], [], (err, group) => { + assert.ok(!err); + + // Fill up the group library with 3 content items. + RestAPI.Content.createLink( + restCtx, + 'name', + 'description', + 'private', + 'http://www.oaeproject.org', + [group.id], + null, + [], + (err, privateContent) => { + assert.ok(!err); + RestAPI.Content.createLink( + restCtx, + 'name', + 'description', + 'loggedin', + 'http://www.oaeproject.org', + [group.id], + null, + [], + (err, loggedinContent) => { + assert.ok(!err); + RestAPI.Content.createLink( + restCtx, + 'name', + 'description', + 'public', + 'http://www.oaeproject.org', + [group.id], + null, + [], + (err, publicContent) => { + assert.ok(!err); + callback(group, privateContent, loggedinContent, publicContent); + } + ); + } + ); + } + ); + }); }; /** @@ -779,30 +655,15 @@ describe('Content Libraries', () => { createUserAndLibrary( camAdminRestCtx, 'private', - ( - privateUser, - privateUserPrivateContent, - privateUserLoggedinContent, - privateUserPublicContent - ) => { + (privateUser, privateUserPrivateContent, privateUserLoggedinContent, privateUserPublicContent) => { createUserAndLibrary( camAdminRestCtx, 'loggedin', - ( - loggedinUser, - loggedinUserPrivateContent, - loggedinUserLoggedinContent, - loggedinUserPublicContent - ) => { + (loggedinUser, loggedinUserPrivateContent, loggedinUserLoggedinContent, loggedinUserPublicContent) => { createUserAndLibrary( camAdminRestCtx, 'public', - ( - publicUser, - publicUserPrivateContent, - publicUserLoggedinContent, - publicUserPublicContent - ) => { + (publicUser, publicUserPrivateContent, publicUserLoggedinContent, publicUserPublicContent) => { // Each user should be able to see all the items in his library. checkLibrary( privateUser.restContext, @@ -814,21 +675,13 @@ describe('Content Libraries', () => { loggedinUser.restContext, loggedinUser.user.id, true, - [ - loggedinUserPublicContent, - loggedinUserLoggedinContent, - loggedinUserPrivateContent - ], + [loggedinUserPublicContent, loggedinUserLoggedinContent, loggedinUserPrivateContent], () => { checkLibrary( publicUser.restContext, publicUser.user.id, true, - [ - publicUserPublicContent, - publicUserLoggedinContent, - publicUserPrivateContent - ], + [publicUserPublicContent, publicUserLoggedinContent, publicUserPrivateContent], () => { // The anonymous user can only see the public stream of the public user. checkLibrary( @@ -837,193 +690,143 @@ describe('Content Libraries', () => { true, [publicUserPublicContent], () => { - checkLibrary( - camAnonymousRestCtx, - loggedinUser.user.id, - false, - [], - () => { + checkLibrary(camAnonymousRestCtx, loggedinUser.user.id, false, [], () => { + checkLibrary(camAnonymousRestCtx, privateUser.user.id, false, [], () => { checkLibrary( - camAnonymousRestCtx, - privateUser.user.id, - false, - [], + gtAnonymousRestCtx, + publicUser.user.id, + true, + [publicUserPublicContent], () => { - checkLibrary( - gtAnonymousRestCtx, - publicUser.user.id, - true, - [publicUserPublicContent], - () => { - checkLibrary( - gtAnonymousRestCtx, - loggedinUser.user.id, - false, - [], - () => { - checkLibrary( - gtAnonymousRestCtx, - privateUser.user.id, - false, - [], - () => { - // A loggedin user on the same tenant can see the loggedin stream for the public and loggedin user. - TestsUtil.generateTestUsers( - camAdminRestCtx, - 1, - (err, users) => { - const { 0: anotherUser } = _.values(users); - checkLibrary( - anotherUser.restContext, - publicUser.user.id, - true, - [ - publicUserPublicContent, - publicUserLoggedinContent - ], - () => { - checkLibrary( - anotherUser.restContext, - loggedinUser.user.id, - true, - [ - loggedinUserPublicContent, - loggedinUserLoggedinContent - ], - () => { - checkLibrary( - anotherUser.restContext, - privateUser.user.id, - false, - [], - () => { - // A loggedin user on *another* tenant can only see the public stream for the public user. - TestsUtil.generateTestUsers( - gtAdminRestCtx, - 1, - (err, users) => { - const { - 0: otherTenantUser - } = _.values(users); - checkLibrary( - otherTenantUser.restContext, - publicUser.user.id, - true, - [publicUserPublicContent], - () => { - checkLibrary( - otherTenantUser.restContext, - loggedinUser.user.id, - false, - [], - () => { - checkLibrary( - otherTenantUser.restContext, - privateUser.user - .id, - false, - [], - () => { - // The cambridge tenant admin can see all the things. - checkLibrary( - camAdminRestCtx, - publicUser - .user.id, - true, - [ - publicUserPublicContent, - publicUserLoggedinContent, - publicUserPrivateContent - ], - () => { - checkLibrary( - camAdminRestCtx, - loggedinUser - .user - .id, - true, - [ - loggedinUserPublicContent, - loggedinUserLoggedinContent, - loggedinUserPrivateContent - ], - () => { - checkLibrary( - camAdminRestCtx, - privateUser - .user - .id, - true, - [ - privateUserPublicContent, - privateUserLoggedinContent, - privateUserPrivateContent - ], - () => { - // The GT tenant admin can only see the public stream for the public user. - checkLibrary( - gtAdminRestCtx, - publicUser - .user - .id, - true, - [ - publicUserPublicContent - ], - () => { - checkLibrary( - gtAdminRestCtx, - loggedinUser - .user - .id, - false, - [], - () => { - checkLibrary( - gtAdminRestCtx, - privateUser - .user - .id, - false, - [], - callback - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + checkLibrary(gtAnonymousRestCtx, loggedinUser.user.id, false, [], () => { + checkLibrary(gtAnonymousRestCtx, privateUser.user.id, false, [], () => { + // A loggedin user on the same tenant can see the loggedin stream for the public and loggedin user. + TestsUtil.generateTestUsers(camAdminRestCtx, 1, (err, users) => { + const { 0: anotherUser } = _.values(users); + checkLibrary( + anotherUser.restContext, + publicUser.user.id, + true, + [publicUserPublicContent, publicUserLoggedinContent], + () => { + checkLibrary( + anotherUser.restContext, + loggedinUser.user.id, + true, + [loggedinUserPublicContent, loggedinUserLoggedinContent], + () => { + checkLibrary( + anotherUser.restContext, + privateUser.user.id, + false, + [], + () => { + // A loggedin user on *another* tenant can only see the public stream for the public user. + TestsUtil.generateTestUsers( + gtAdminRestCtx, + 1, + (err, users) => { + const { 0: otherTenantUser } = _.values(users); + checkLibrary( + otherTenantUser.restContext, + publicUser.user.id, + true, + [publicUserPublicContent], + () => { + checkLibrary( + otherTenantUser.restContext, + loggedinUser.user.id, + false, + [], + () => { + checkLibrary( + otherTenantUser.restContext, + privateUser.user.id, + false, + [], + () => { + // The cambridge tenant admin can see all the things. + checkLibrary( + camAdminRestCtx, + publicUser.user.id, + true, + [ + publicUserPublicContent, + publicUserLoggedinContent, + publicUserPrivateContent + ], + () => { + checkLibrary( + camAdminRestCtx, + loggedinUser.user.id, + true, + [ + loggedinUserPublicContent, + loggedinUserLoggedinContent, + loggedinUserPrivateContent + ], + () => { + checkLibrary( + camAdminRestCtx, + privateUser.user.id, + true, + [ + privateUserPublicContent, + privateUserLoggedinContent, + privateUserPrivateContent + ], + () => { + // The GT tenant admin can only see the public stream for the public user. + checkLibrary( + gtAdminRestCtx, + publicUser.user.id, + true, + [publicUserPublicContent], + () => { + checkLibrary( + gtAdminRestCtx, + loggedinUser.user.id, + false, + [], + () => { + checkLibrary( + gtAdminRestCtx, + privateUser.user.id, + false, + [], + callback + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); + }); + }); } ); - } - ); + }); + }); } ); } @@ -1052,157 +855,138 @@ describe('Content Libraries', () => { createGroupAndLibrary( groupCreator.restContext, 'private', - ( - privateGroup, - privateGroupPrivateContent, - privateGroupLoggedinContent, - privateGroupPublicContent - ) => { + (privateGroup, privateGroupPrivateContent, privateGroupLoggedinContent, privateGroupPublicContent) => { createGroupAndLibrary( groupCreator.restContext, 'loggedin', - ( - loggedinGroup, - loggedinGroupPrivateContent, - loggedinGroupLoggedinContent, - loggedinGroupPublicContent - ) => { + (loggedinGroup, loggedinGroupPrivateContent, loggedinGroupLoggedinContent, loggedinGroupPublicContent) => { createGroupAndLibrary( groupCreator.restContext, 'public', - ( - publicGroup, - publicGroupPrivateContent, - publicGroupLoggedinContent, - publicGroupPublicContent - ) => { + (publicGroup, publicGroupPrivateContent, publicGroupLoggedinContent, publicGroupPublicContent) => { // An anonymous user can only see the public stream for the public group. - checkLibrary( - camAnonymousRestCtx, - publicGroup.id, - true, - [publicGroupPublicContent], - () => { - checkLibrary(camAnonymousRestCtx, loggedinGroup.id, false, [], () => { - checkLibrary(camAnonymousRestCtx, privateGroup.id, false, [], () => { - checkLibrary( - gtAnonymousRestCtx, - publicGroup.id, - true, - [publicGroupPublicContent], - () => { - checkLibrary(gtAnonymousRestCtx, loggedinGroup.id, false, [], () => { - checkLibrary(gtAnonymousRestCtx, privateGroup.id, false, [], () => { - // A loggedin user on the same tenant can see the loggedin stream for the public and loggedin group. + checkLibrary(camAnonymousRestCtx, publicGroup.id, true, [publicGroupPublicContent], () => { + checkLibrary(camAnonymousRestCtx, loggedinGroup.id, false, [], () => { + checkLibrary(camAnonymousRestCtx, privateGroup.id, false, [], () => { + checkLibrary(gtAnonymousRestCtx, publicGroup.id, true, [publicGroupPublicContent], () => { + checkLibrary(gtAnonymousRestCtx, loggedinGroup.id, false, [], () => { + checkLibrary(gtAnonymousRestCtx, privateGroup.id, false, [], () => { + // A loggedin user on the same tenant can see the loggedin stream for the public and loggedin group. + checkLibrary( + anotherUser.restContext, + publicGroup.id, + true, + [publicGroupPublicContent, publicGroupLoggedinContent], + () => { checkLibrary( anotherUser.restContext, - publicGroup.id, + loggedinGroup.id, true, - [publicGroupPublicContent, publicGroupLoggedinContent], + [loggedinGroupPublicContent, loggedinGroupLoggedinContent], () => { - checkLibrary( - anotherUser.restContext, - loggedinGroup.id, - true, - [loggedinGroupPublicContent, loggedinGroupLoggedinContent], - () => { + checkLibrary(anotherUser.restContext, privateGroup.id, false, [], () => { + // A loggedin user on *another* tenant can only see the public stream for the public group. + TestsUtil.generateTestUsers(gtAdminRestCtx, 1, (err, users) => { + const otherTenantUser = _.values(users)[0]; checkLibrary( - anotherUser.restContext, - privateGroup.id, - false, - [], + otherTenantUser.restContext, + publicGroup.id, + true, + [publicGroupPublicContent], () => { - // A loggedin user on *another* tenant can only see the public stream for the public group. - TestsUtil.generateTestUsers( - gtAdminRestCtx, - 1, - (err, users) => { - const otherTenantUser = _.values(users)[0]; + checkLibrary( + otherTenantUser.restContext, + loggedinGroup.id, + false, + [], + () => { checkLibrary( otherTenantUser.restContext, - publicGroup.id, - true, - [publicGroupPublicContent], + privateGroup.id, + false, + [], () => { + // The cambridge tenant admin can see all the things. checkLibrary( - otherTenantUser.restContext, - loggedinGroup.id, - false, - [], + camAdminRestCtx, + publicGroup.id, + true, + [ + publicGroupPublicContent, + publicGroupLoggedinContent, + publicGroupPrivateContent + ], () => { checkLibrary( - otherTenantUser.restContext, - privateGroup.id, - false, - [], + camAdminRestCtx, + loggedinGroup.id, + true, + [ + loggedinGroupPublicContent, + loggedinGroupLoggedinContent, + loggedinGroupPrivateContent + ], () => { - // The cambridge tenant admin can see all the things. checkLibrary( camAdminRestCtx, - publicGroup.id, + privateGroup.id, true, [ - publicGroupPublicContent, - publicGroupLoggedinContent, - publicGroupPrivateContent + privateGroupPrivateContent, + privateGroupLoggedinContent, + privateGroupPrivateContent ], () => { + // The GT tenant admin can only see the public stream for the public group. checkLibrary( - camAdminRestCtx, - loggedinGroup.id, + gtAdminRestCtx, + publicGroup.id, true, - [ - loggedinGroupPublicContent, - loggedinGroupLoggedinContent, - loggedinGroupPrivateContent - ], + [publicGroupPublicContent], () => { checkLibrary( - camAdminRestCtx, - privateGroup.id, - true, - [ - privateGroupPrivateContent, - privateGroupLoggedinContent, - privateGroupPrivateContent - ], + gtAdminRestCtx, + loggedinGroup.id, + false, + [], () => { - // The GT tenant admin can only see the public stream for the public group. checkLibrary( gtAdminRestCtx, - publicGroup.id, - true, - [ - publicGroupPublicContent - ], + privateGroup.id, + false, + [], () => { - checkLibrary( - gtAdminRestCtx, - loggedinGroup.id, - false, - [], - () => { + // If we make the cambridge user a member of the private group he should see everything. + let changes = {}; + changes[anotherUser.user.id] = 'member'; + RestAPI.Group.setGroupMembers( + groupCreator.restContext, + privateGroup.id, + changes, + err => { + assert.ok(!err); checkLibrary( - gtAdminRestCtx, + anotherUser.restContext, privateGroup.id, - false, - [], + true, + [ + privateGroupPrivateContent, + privateGroupLoggedinContent, + privateGroupPrivateContent + ], () => { - // If we make the cambridge user a member of the private group he should see everything. - let changes = {}; - changes[ - anotherUser.user.id - ] = 'member'; + // If we make the GT user a member of the private group, he should see everything. + changes = {}; + changes[otherTenantUser.user.id] = + 'member'; RestAPI.Group.setGroupMembers( groupCreator.restContext, privateGroup.id, changes, err => { - assert.ok( - !err - ); + assert.ok(!err); checkLibrary( - anotherUser.restContext, + otherTenantUser.restContext, privateGroup.id, true, [ @@ -1210,35 +994,7 @@ describe('Content Libraries', () => { privateGroupLoggedinContent, privateGroupPrivateContent ], - () => { - // If we make the GT user a member of the private group, he should see everything. - changes = {}; - changes[ - otherTenantUser.user.id - ] = - 'member'; - RestAPI.Group.setGroupMembers( - groupCreator.restContext, - privateGroup.id, - changes, - err => { - assert.ok( - !err - ); - checkLibrary( - otherTenantUser.restContext, - privateGroup.id, - true, - [ - privateGroupPrivateContent, - privateGroupLoggedinContent, - privateGroupPrivateContent - ], - callback - ); - } - ); - } + callback ); } ); @@ -1264,18 +1020,18 @@ describe('Content Libraries', () => { ); } ); - } - ); + }); + }); } ); - }); - }); - } - ); + } + ); + }); + }); }); }); - } - ); + }); + }); } ); } @@ -1289,46 +1045,26 @@ describe('Content Libraries', () => { * Test that verifies that a library can be rebuilt from a dirty authz table */ it('verify a library can be rebuilt from a dirty authz table', callback => { - createUserAndLibrary( - camAdminRestCtx, - 'private', - (simong, privateContent, loggedinContent, publicContent) => { - // Ensure all the items are in the user's library - checkLibrary( - simong.restContext, - simong.user.id, - true, - [privateContent, loggedinContent, publicContent], - () => { - // Remove a content item directly in Cassandra. This will leave a pointer - // in the Authz table that points to nothing. The library re-indexer should - // be able to deal with this. Note that we go straight to Cassandra, as the - // ContentDAO also takes care of removing the item from the appropriate libraries - Cassandra.runQuery( - 'DELETE FROM "Content" WHERE "contentId" = ?', - [privateContent.id], - err => { - assert.ok(!err); + createUserAndLibrary(camAdminRestCtx, 'private', (simong, privateContent, loggedinContent, publicContent) => { + // Ensure all the items are in the user's library + checkLibrary(simong.restContext, simong.user.id, true, [privateContent, loggedinContent, publicContent], () => { + // Remove a content item directly in Cassandra. This will leave a pointer + // in the Authz table that points to nothing. The library re-indexer should + // be able to deal with this. Note that we go straight to Cassandra, as the + // ContentDAO also takes care of removing the item from the appropriate libraries + Cassandra.runQuery('DELETE FROM "Content" WHERE "contentId" = ?', [privateContent.id], err => { + assert.ok(!err); - // Purge the library so that it has to be rebuild on the next request - LibraryAPI.Index.purge('content:content', simong.user.id, err => { - assert.ok(!err); + // Purge the library so that it has to be rebuild on the next request + LibraryAPI.Index.purge('content:content', simong.user.id, err => { + assert.ok(!err); - // We should be able to rebuild the library on-the-fly. The private - // content item should not be returned as it has been removed - checkLibrary( - simong.restContext, - simong.user.id, - true, - [loggedinContent, publicContent], - callback - ); - }); - } - ); - } - ); - } - ); + // We should be able to rebuild the library on-the-fly. The private + // content item should not be returned as it has been removed + checkLibrary(simong.restContext, simong.user.id, true, [loggedinContent, publicContent], callback); + }); + }); + }); + }); }); }); diff --git a/packages/oae-content/tests/test-previews.js b/packages/oae-content/tests/test-previews.js index 1b2c5376ff..9af2f9eafc 100644 --- a/packages/oae-content/tests/test-previews.js +++ b/packages/oae-content/tests/test-previews.js @@ -12,20 +12,19 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ +/* eslint-disable camelcase */ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const url = require('url'); -const _ = require('underscore'); +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import url from 'url'; +import _ from 'underscore'; -const ActivityTestsUtil = require('oae-activity/lib/test/util'); -const ConfigTestUtil = require('oae-config/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const RestUtil = require('oae-rest/lib/util'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); +import * as ActivityTestsUtil from 'oae-activity/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as RestUtil from 'oae-rest/lib/util'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests'; describe('File previews', () => { // Rest context that can be used every time we need to make a request as a global admin @@ -47,9 +46,7 @@ describe('File previews', () => { // Fill up global admin rest context globalAdminRestContext = TestsUtil.createGlobalAdminRestContext(); // Fill up the anonymous context - anonymousRestContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.localhost.host - ); + anonymousRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.localhost.host); // Cambridge tenant admin context camAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.cam.host); @@ -152,21 +149,17 @@ describe('File previews', () => { assert.strictEqual(previews.files.length, 2); // Ensure that the thumbnail and status parameters are set - RestAPI.Content.getContent( - contexts.nicolaas.restContext, - contentObj.id, - (err, updatedContent) => { - assert.ok(!err); - assert.ok(!updatedContent.previews.thumbnailUri); - assert.ok(updatedContent.previews.thumbnailUrl); - assert.strictEqual(updatedContent.previews.status, 'done'); - assert.ok(!updatedContent.previews.smallUri); - assert.ok(updatedContent.previews.smallUrl); - assert.ok(!updatedContent.previews.mediumUri); - assert.ok(updatedContent.previews.mediumUrl); - return callback(contexts, updatedContent, previews); - } - ); + RestAPI.Content.getContent(contexts.nicolaas.restContext, contentObj.id, (err, updatedContent) => { + assert.ok(!err); + assert.ok(!updatedContent.previews.thumbnailUri); + assert.ok(updatedContent.previews.thumbnailUrl); + assert.strictEqual(updatedContent.previews.status, 'done'); + assert.ok(!updatedContent.previews.smallUri); + assert.ok(updatedContent.previews.smallUrl); + assert.ok(!updatedContent.previews.mediumUri); + assert.ok(updatedContent.previews.mediumUrl); + return callback(contexts, updatedContent, previews); + }); } ); } @@ -246,96 +239,76 @@ describe('File previews', () => { assert.ok(!err); // Update the file body, creating a new revision - RestAPI.Content.updateFileBody( - contexts.nicolaas.restContext, - content.id, - getFileStream, - (err, content) => { - assert.ok(!err); - - // Finish processing the previews for the new revision - RestAPI.Content.setPreviewItems( - globalAdminOnTenantRestContext, - content.id, - content.latestRevisionId, - 'done', - suitable_files, - suitable_sizes, - {}, - {}, - err => { - assert.ok(!err); - - // Restore to the first revision - RestAPI.Content.restoreRevision( - contexts.nicolaas.restContext, - content.id, - firstRevisionId, - (err, revision3) => { - assert.ok(!err); - - // Get the preview items of the 3rd revision (restored from first), and verify that the model is the same - RestAPI.Content.getPreviewItems( - contexts.nicolaas.restContext, - content.id, - revision3.revisionId, - (err, thirdRevisionPreviews) => { - assert.ok(!err); - assert.strictEqual( - firstRevisionPreviews.files.length, - thirdRevisionPreviews.files.length - ); - - // Get the medium picture of the first and third revisions - const firstRevisionMediumPicture = _.filter( - firstRevisionPreviews.files, - file => { - return file.size === 'medium'; - } - )[0]; - const thirdRevisionMediumPicture = _.filter( - thirdRevisionPreviews.files, - file => { - return file.size === 'medium'; - } - )[0]; - - assert.ok(firstRevisionMediumPicture); - assert.ok(thirdRevisionMediumPicture); - assert.strictEqual( - firstRevisionMediumPicture.filename, - thirdRevisionMediumPicture.filename - ); - assert.strictEqual( - firstRevisionMediumPicture.uri, - thirdRevisionMediumPicture.uri - ); + RestAPI.Content.updateFileBody(contexts.nicolaas.restContext, content.id, getFileStream, (err, content) => { + assert.ok(!err); - // Verify that we can download the preview pictures of the new revision - RestAPI.Content.downloadPreviewItem( - contexts.nicolaas.restContext, - content.id, - revision3.revisionId, - thirdRevisionMediumPicture.filename, - thirdRevisionPreviews.signature, - (err, body, response) => { - assert.ok(!err); - assert.strictEqual(response.statusCode, 204); - assert.ok(response.headers['x-accel-redirect']); + // Finish processing the previews for the new revision + RestAPI.Content.setPreviewItems( + globalAdminOnTenantRestContext, + content.id, + content.latestRevisionId, + 'done', + suitable_files, + suitable_sizes, + {}, + {}, + err => { + assert.ok(!err); - // Nginx streams the file body, so there will be no body to this response here - assert.ok(!body); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + // Restore to the first revision + RestAPI.Content.restoreRevision( + contexts.nicolaas.restContext, + content.id, + firstRevisionId, + (err, revision3) => { + assert.ok(!err); + + // Get the preview items of the 3rd revision (restored from first), and verify that the model is the same + RestAPI.Content.getPreviewItems( + contexts.nicolaas.restContext, + content.id, + revision3.revisionId, + (err, thirdRevisionPreviews) => { + assert.ok(!err); + assert.strictEqual(firstRevisionPreviews.files.length, thirdRevisionPreviews.files.length); + + // Get the medium picture of the first and third revisions + const firstRevisionMediumPicture = _.filter(firstRevisionPreviews.files, file => { + return file.size === 'medium'; + })[0]; + const thirdRevisionMediumPicture = _.filter(thirdRevisionPreviews.files, file => { + return file.size === 'medium'; + })[0]; + + assert.ok(firstRevisionMediumPicture); + assert.ok(thirdRevisionMediumPicture); + assert.strictEqual(firstRevisionMediumPicture.filename, thirdRevisionMediumPicture.filename); + assert.strictEqual(firstRevisionMediumPicture.uri, thirdRevisionMediumPicture.uri); + + // Verify that we can download the preview pictures of the new revision + RestAPI.Content.downloadPreviewItem( + contexts.nicolaas.restContext, + content.id, + revision3.revisionId, + thirdRevisionMediumPicture.filename, + thirdRevisionPreviews.signature, + (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 204); + assert.ok(response.headers['x-accel-redirect']); + + // Nginx streams the file body, so there will be no body to this response here + assert.ok(!body); + return callback(); + } + ); + } + ); + } + ); + } + ); + }); } ); }); @@ -414,92 +387,86 @@ describe('File previews', () => { assert.ok(contentObj.id); assert.strictEqual(contentObj.previews.status, 'pending'); - RestAPI.Content.getRevisions( - simon.restContext, - contentObj.id, - null, - 1, - (err, revisions) => { - assert.ok(!err); - const { revisionId } = revisions.results[0]; - - // A valid call as a sanity check. - RestAPI.Content.setPreviewItems( - globalAdminOnTenantRestContext, - contentObj.id, - revisionId, - 'done', - {}, - {}, - {}, - {}, - err => { - assert.ok(!err); - - // Invalid contentId. - RestAPI.Content.setPreviewItems( - globalAdminOnTenantRestContext, - 'blah', - revisionId, - 'foo', - {}, - {}, - {}, - {}, - err => { - assert.strictEqual(err.code, 400); - - // Bad status parameter. - RestAPI.Content.setPreviewItems( - globalAdminOnTenantRestContext, - contentObj.id, - revisionId, - 'foo', - {}, - {}, - {}, - {}, - err => { - assert.strictEqual(err.code, 400); + RestAPI.Content.getRevisions(simon.restContext, contentObj.id, null, 1, (err, revisions) => { + assert.ok(!err); + const { revisionId } = revisions.results[0]; + + // A valid call as a sanity check. + RestAPI.Content.setPreviewItems( + globalAdminOnTenantRestContext, + contentObj.id, + revisionId, + 'done', + {}, + {}, + {}, + {}, + err => { + assert.ok(!err); - // Non existing piece of content. - RestAPI.Content.setPreviewItems( - globalAdminOnTenantRestContext, - 'c:foo:bar', - revisionId, - 'done', - {}, - {}, - {}, - {}, - err => { - assert.strictEqual(err.code, 404); - - // Missing revision - RestAPI.Content.setPreviewItems( - globalAdminOnTenantRestContext, - 'c:foo:bar', - null, - 'done', - {}, - {}, - {}, - {}, - err => { - assert.strictEqual(err.code, 404); - callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + // Invalid contentId. + RestAPI.Content.setPreviewItems( + globalAdminOnTenantRestContext, + 'blah', + revisionId, + 'foo', + {}, + {}, + {}, + {}, + err => { + assert.strictEqual(err.code, 400); + + // Bad status parameter. + RestAPI.Content.setPreviewItems( + globalAdminOnTenantRestContext, + contentObj.id, + revisionId, + 'foo', + {}, + {}, + {}, + {}, + err => { + assert.strictEqual(err.code, 400); + + // Non existing piece of content. + RestAPI.Content.setPreviewItems( + globalAdminOnTenantRestContext, + 'c:foo:bar', + revisionId, + 'done', + {}, + {}, + {}, + {}, + err => { + assert.strictEqual(err.code, 404); + + // Missing revision + RestAPI.Content.setPreviewItems( + globalAdminOnTenantRestContext, + 'c:foo:bar', + null, + 'done', + {}, + {}, + {}, + {}, + err => { + assert.strictEqual(err.code, 404); + callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + }); } ); }); @@ -527,40 +494,30 @@ describe('File previews', () => { assert.ok(contentObj.id); assert.strictEqual(contentObj.previews.status, 'pending'); - RestAPI.Content.getRevisions( - globalAdminOnTenantRestContext, - contentObj.id, - null, - 1, - (err, revisions) => { - assert.ok(!err); - const { revisionId } = revisions.results[0]; + RestAPI.Content.getRevisions(globalAdminOnTenantRestContext, contentObj.id, null, 1, (err, revisions) => { + assert.ok(!err); + const { revisionId } = revisions.results[0]; + + RestAPI.Content.setPreviewItems( + globalAdminOnTenantRestContext, + contentObj.id, + revisionId, + 'ignored', + {}, + {}, + {}, + {}, + err => { + assert.ok(!err); - RestAPI.Content.setPreviewItems( - globalAdminOnTenantRestContext, - contentObj.id, - revisionId, - 'ignored', - {}, - {}, - {}, - {}, - err => { + RestAPI.Content.getContent(simon.restContext, contentObj.id, (err, updatedContentObj) => { assert.ok(!err); - - RestAPI.Content.getContent( - simon.restContext, - contentObj.id, - (err, updatedContentObj) => { - assert.ok(!err); - assert.strictEqual(updatedContentObj.previews.status, 'ignored'); - callback(); - } - ); - } - ); - } - ); + assert.strictEqual(updatedContentObj.previews.status, 'ignored'); + callback(); + }); + } + ); + }); } ); }); @@ -593,18 +550,14 @@ describe('File previews', () => { assert.ok(!err); assert.strictEqual(previews.files.length, 0); - RestAPI.Content.getContent( - contexts.nicolaas.restContext, - contentObj.id, - (err, content) => { - assert.ok(!err); - assert.strictEqual(content.previews.total, 0); - assert.strictEqual(content.previews.status, 'error'); - assert.ok(!content.previews.thumbnailUri); - assert.ok(!content.previews.thumbnailUrl); - callback(); - } - ); + RestAPI.Content.getContent(contexts.nicolaas.restContext, contentObj.id, (err, content) => { + assert.ok(!err); + assert.strictEqual(content.previews.total, 0); + assert.strictEqual(content.previews.status, 'error'); + assert.ok(!content.previews.thumbnailUri); + assert.ok(!content.previews.thumbnailUrl); + callback(); + }); } ); } @@ -640,19 +593,15 @@ describe('File previews', () => { assert.ok(!err); assert.strictEqual(previews.files.length, 1); - RestAPI.Content.getContent( - contexts.nicolaas.restContext, - contentObj.id, - (err, content) => { - assert.ok(!err); - assert.strictEqual(content.previews.total, 1); - assert.ok(!content.previews.thumbnailUri); - assert.ok(!content.previews.thumbnailUrl); - assert.ok(!content.previews.smallUri); - assert.ok(content.previews.smallUrl); - return callback(); - } - ); + RestAPI.Content.getContent(contexts.nicolaas.restContext, contentObj.id, (err, content) => { + assert.ok(!err); + assert.strictEqual(content.previews.total, 1); + assert.ok(!content.previews.thumbnailUri); + assert.ok(!content.previews.thumbnailUrl); + assert.ok(!content.previews.smallUri); + assert.ok(content.previews.smallUrl); + return callback(); + }); } ); } @@ -690,47 +639,39 @@ describe('File previews', () => { { link: 'http://www.google.com' }, err => { assert.ok(!err); - RestAPI.Content.getContent( - contexts.nicolaas.restContext, - contentObj.id, - (err, contentObj) => { - assert.ok(!err); - assert.strictEqual(contentObj.previews.status, 'pending'); - - // Verify that an update with the same link doesn't change the previews object - // First, set the status to done manually so we can verify a no-change on a non-update - RestAPI.Content.setPreviewItems( - globalAdminOnTenantRestContext, - contentObj.id, - contentObj.latestRevisionId, - 'done', - {}, - {}, - {}, - {}, - err => { - assert.ok(!err); - RestAPI.Content.updateContent( - contexts.nicolaas.restContext, - contentObj.id, - { link: 'http://www.google.com' }, - err => { + RestAPI.Content.getContent(contexts.nicolaas.restContext, contentObj.id, (err, contentObj) => { + assert.ok(!err); + assert.strictEqual(contentObj.previews.status, 'pending'); + + // Verify that an update with the same link doesn't change the previews object + // First, set the status to done manually so we can verify a no-change on a non-update + RestAPI.Content.setPreviewItems( + globalAdminOnTenantRestContext, + contentObj.id, + contentObj.latestRevisionId, + 'done', + {}, + {}, + {}, + {}, + err => { + assert.ok(!err); + RestAPI.Content.updateContent( + contexts.nicolaas.restContext, + contentObj.id, + { link: 'http://www.google.com' }, + err => { + assert.ok(!err); + RestAPI.Content.getContent(contexts.nicolaas.restContext, contentObj.id, (err, contentObj) => { assert.ok(!err); - RestAPI.Content.getContent( - contexts.nicolaas.restContext, - contentObj.id, - (err, contentObj) => { - assert.ok(!err); - assert.strictEqual(contentObj.previews.status, 'done'); - return callback(); - } - ); - } - ); - } - ); - } - ); + assert.strictEqual(contentObj.previews.status, 'done'); + return callback(); + }); + } + ); + } + ); + }); } ); } @@ -823,26 +764,20 @@ describe('File previews', () => { }); /*! - * Verifies that the `downloadUrl` can in fact be downloaded - * - * @param {RestContext} restContext The RestContext that we should use to download the file - * @param {String} downloadUrl The signed URL that should be verified - * @param {Function} callback Standard callback function - */ + * Verifies that the `downloadUrl` can in fact be downloaded + * + * @param {RestContext} restContext The RestContext that we should use to download the file + * @param {String} downloadUrl The signed URL that should be verified + * @param {Function} callback Standard callback function + */ const _verifySignedDownloadUrl = function(restContext, downloadUrl, callback) { // Verify we can download it const parsedUrl = url.parse(downloadUrl, true); - RestUtil.performRestRequest( - restContext, - '/api/download/signed', - 'GET', - parsedUrl.query, - (err, body, response) => { - assert.ok(!err); - assert.strictEqual(response.statusCode, 204); - return callback(); - } - ); + RestUtil.performRestRequest(restContext, '/api/download/signed', 'GET', parsedUrl.query, (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 204); + return callback(); + }); }; /** @@ -856,79 +791,57 @@ describe('File previews', () => { TestsUtil.generateTestUsers(camAdminRestContext, 1, (err, users) => { assert.ok(!err); const bert = _.values(users)[0]; - RestAPI.Content.shareContent( - contexts.nicolaas.restContext, - contentObj.id, - [bert.user.id], - err => { + RestAPI.Content.shareContent(contexts.nicolaas.restContext, contentObj.id, [bert.user.id], err => { + assert.ok(!err); + + // Bert should receive an activity that Nicolaas shared a piece of content with him + ActivityTestsUtil.collectAndGetActivityStream(bert.restContext, bert.user.id, null, (err, activityStream) => { assert.ok(!err); - // Bert should receive an activity that Nicolaas shared a piece of content with him - ActivityTestsUtil.collectAndGetActivityStream( - bert.restContext, - bert.user.id, - null, - (err, activityStream) => { - assert.ok(!err); + const activity = _.find(activityStream.items, activity => { + return activity.object['oae:id'] === contentObj.id; + }); + assert.ok(activity); - const activity = _.find(activityStream.items, activity => { - return activity.object['oae:id'] === contentObj.id; - }); - assert.ok(activity); + // Verify the activity + _verifySignedDownloadUrl(bert.restContext, activity.object.image.url, () => { + // Verify the thumbnailUrl is on the content profile, but not the back-end uri + RestAPI.Content.getContent(bert.restContext, contentObj.id, (err, contentObjOnCamTenant) => { + assert.ok(!err); + assert.ok(!contentObjOnCamTenant.previews.thumbnailUri); + assert.ok(contentObjOnCamTenant.previews.thumbnailUrl); - // Verify the activity - _verifySignedDownloadUrl(bert.restContext, activity.object.image.url, () => { - // Verify the thumbnailUrl is on the content profile, but not the back-end uri - RestAPI.Content.getContent( - bert.restContext, + _verifySignedDownloadUrl(bert.restContext, contentObjOnCamTenant.previews.thumbnailUrl, () => { + // Verify the thumbnailUrl in search results + const randomText = TestsUtil.generateRandomText(5); + RestAPI.Content.updateContent( + contexts.nicolaas.restContext, contentObj.id, - (err, contentObjOnCamTenant) => { + { displayName: randomText }, + (err, updatedContentObj) => { assert.ok(!err); - assert.ok(!contentObjOnCamTenant.previews.thumbnailUri); - assert.ok(contentObjOnCamTenant.previews.thumbnailUrl); - - _verifySignedDownloadUrl( + SearchTestsUtil.searchAll( bert.restContext, - contentObjOnCamTenant.previews.thumbnailUrl, - () => { - // Verify the thumbnailUrl in search results - const randomText = TestsUtil.generateRandomText(5); - RestAPI.Content.updateContent( - contexts.nicolaas.restContext, - contentObj.id, - { displayName: randomText }, - (err, updatedContentObj) => { - assert.ok(!err); - SearchTestsUtil.searchAll( - bert.restContext, - 'general', - null, - { resourceTypes: 'content', q: randomText }, - (err, results) => { - assert.ok(!err); - const doc = _.find(results.results, doc => { - return doc.id === contentObj.id; - }); - assert.ok(doc); - - return _verifySignedDownloadUrl( - bert.restContext, - doc.thumbnailUrl, - callback - ); - } - ); - } - ); + 'general', + null, + { resourceTypes: 'content', q: randomText }, + (err, results) => { + assert.ok(!err); + const doc = _.find(results.results, doc => { + return doc.id === contentObj.id; + }); + assert.ok(doc); + + return _verifySignedDownloadUrl(bert.restContext, doc.thumbnailUrl, callback); } ); } ); }); - } - ); - } - ); + }); + }); + }); + }); }); }); }); @@ -939,77 +852,55 @@ describe('File previews', () => { it('verify thumbnail and medium URLs are present on the revision object', callback => { createPreviews((contexts, content, previews) => { // Verify a list of revisions - RestAPI.Content.getRevisions( - contexts.nicolaas.restContext, - content.id, - null, - null, - (err, revisions) => { - assert.ok(!err); - assert.ok(!revisions.results[0].thumbnailUri); - assert.ok(revisions.results[0].thumbnailUrl); - assert.ok(!revisions.results[0].mediumUri); - assert.ok(revisions.results[0].mediumUrl); - - _verifySignedDownloadUrl( - contexts.nicolaas.restContext, - revisions.results[0].thumbnailUrl, - () => { - _verifySignedDownloadUrl( - contexts.nicolaas.restContext, - revisions.results[0].mediumUrl, - () => { - // Verify a single revision - RestAPI.Content.getRevision( - contexts.nicolaas.restContext, - content.id, - revisions.results[0].revisionId, - (err, revision) => { - assert.ok(!err); - assert.ok(!revision.thumbnailUri); - assert.ok(revision.thumbnailUrl); - assert.ok(!revision.mediumUri); - assert.ok(revision.mediumUrl); - - // Verify the URLs can resolve to a successful response - _verifySignedDownloadUrl( - contexts.nicolaas.restContext, - revision.thumbnailUrl, - () => { - _verifySignedDownloadUrl( - contexts.nicolaas.restContext, - revision.mediumUrl, - () => { - // Restore the revision - RestAPI.Content.restoreRevision( - contexts.nicolaas.restContext, - content.id, - revisions.results[0].revisionId, - (err, restoredRevision) => { - assert.ok(!err); - - // Make sure the restored revision contains all the image urls and not the back-end uris - assert.ok(restoredRevision); - assert.ok(!restoredRevision.thumbnailUri); - assert.ok(restoredRevision.thumbnailUrl); - assert.ok(!restoredRevision.mediumUri); - assert.ok(restoredRevision.mediumUrl); - assert.strictEqual(restoredRevision.previews.status, 'done'); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + RestAPI.Content.getRevisions(contexts.nicolaas.restContext, content.id, null, null, (err, revisions) => { + assert.ok(!err); + assert.ok(!revisions.results[0].thumbnailUri); + assert.ok(revisions.results[0].thumbnailUrl); + assert.ok(!revisions.results[0].mediumUri); + assert.ok(revisions.results[0].mediumUrl); + + _verifySignedDownloadUrl(contexts.nicolaas.restContext, revisions.results[0].thumbnailUrl, () => { + _verifySignedDownloadUrl(contexts.nicolaas.restContext, revisions.results[0].mediumUrl, () => { + // Verify a single revision + RestAPI.Content.getRevision( + contexts.nicolaas.restContext, + content.id, + revisions.results[0].revisionId, + (err, revision) => { + assert.ok(!err); + assert.ok(!revision.thumbnailUri); + assert.ok(revision.thumbnailUrl); + assert.ok(!revision.mediumUri); + assert.ok(revision.mediumUrl); + + // Verify the URLs can resolve to a successful response + _verifySignedDownloadUrl(contexts.nicolaas.restContext, revision.thumbnailUrl, () => { + _verifySignedDownloadUrl(contexts.nicolaas.restContext, revision.mediumUrl, () => { + // Restore the revision + RestAPI.Content.restoreRevision( + contexts.nicolaas.restContext, + content.id, + revisions.results[0].revisionId, + (err, restoredRevision) => { + assert.ok(!err); + + // Make sure the restored revision contains all the image urls and not the back-end uris + assert.ok(restoredRevision); + assert.ok(!restoredRevision.thumbnailUri); + assert.ok(restoredRevision.thumbnailUrl); + assert.ok(!restoredRevision.mediumUri); + assert.ok(restoredRevision.mediumUrl); + assert.strictEqual(restoredRevision.previews.status, 'done'); + return callback(); + } + ); + }); + }); + } + ); + }); + }); + }); }); }); @@ -1080,10 +971,7 @@ describe('File previews', () => { }); assert.ok(contentDocB); assert.ok(contentDocB.thumbnailUrl); - assert.notStrictEqual( - contentDocA.thumbnailUrl, - contentDocB.thumbnailUrl - ); + assert.notStrictEqual(contentDocA.thumbnailUrl, contentDocB.thumbnailUrl); return callback(); } ); diff --git a/packages/oae-content/tests/test-push.js b/packages/oae-content/tests/test-push.js index b1472e1a52..113ad629ac 100644 --- a/packages/oae-content/tests/test-push.js +++ b/packages/oae-content/tests/test-push.js @@ -13,18 +13,17 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const _ = require('underscore'); +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import _ from 'underscore'; -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityTestsUtil = require('oae-activity/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const TestsUtil = require('oae-tests'); +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as ActivityTestsUtil from 'oae-activity/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; -const { ContentConstants } = require('oae-content/lib/constants'); +import { ContentConstants } from 'oae-content/lib/constants'; describe('Content Push', () => { // Rest contexts that can be used performing rest requests @@ -34,9 +33,7 @@ describe('Content Push', () => { * Function that will fill up the tenant admin and anymous rest contexts */ before(callback => { - localAdminRestContext = TestsUtil.createTenantAdminRestContext( - global.oaeTests.tenants.localhost.host - ); + localAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.localhost.host); callback(); }); @@ -84,65 +81,47 @@ describe('Content Push', () => { assert.strictEqual(err.code, 400); // Ensure we get a 400 error with an invalid token - client.subscribe( - contentObj.id, - 'activity', - { signature: 'foo' }, - null, - err => { + client.subscribe(contentObj.id, 'activity', { signature: 'foo' }, null, err => { + assert.strictEqual(err.code, 401); + client.subscribe(contentObj.id, 'activity', { expires: Date.now() + 10000 }, null, err => { assert.strictEqual(err.code, 401); + + // Ensure we get a 401 error with an incorrect signature client.subscribe( contentObj.id, 'activity', - { expires: Date.now() + 10000 }, + { expires: Date.now() + 10000, signature: 'foo' }, null, err => { assert.strictEqual(err.code, 401); - // Ensure we get a 401 error with an incorrect signature - client.subscribe( + // Simon should not be able to use a signature that was generated for Branden + RestAPI.Content.getContent( + branden.restContext, contentObj.id, - 'activity', - { expires: Date.now() + 10000, signature: 'foo' }, - null, - err => { - assert.strictEqual(err.code, 401); - - // Simon should not be able to use a signature that was generated for Branden - RestAPI.Content.getContent( - branden.restContext, + (err, contentObjForBranden) => { + assert.ok(!err); + client.subscribe( contentObj.id, - (err, contentObjForBranden) => { - assert.ok(!err); - client.subscribe( - contentObj.id, - 'activity', - contentObjForBranden.signature, - null, - err => { - assert.strictEqual(err.code, 401); - - // Sanity check a valid signature works - client.subscribe( - contentObj.id, - 'activity', - contentObj.signature, - null, - err => { - assert.ok(!err); - return callback(); - } - ); - } - ); + 'activity', + contentObjForBranden.signature, + null, + err => { + assert.strictEqual(err.code, 401); + + // Sanity check a valid signature works + client.subscribe(contentObj.id, 'activity', contentObj.signature, null, err => { + assert.ok(!err); + return callback(); + }); } ); } ); } ); - } - ); + }); + }); }); }); }); @@ -200,48 +179,44 @@ describe('Content Push', () => { [], (err, contentObj) => { assert.ok(!err); - RestAPI.Content.getContent( - contexts.simon.restContext, - contentObj.id, - (err, contentObj) => { - assert.ok(!err); + RestAPI.Content.getContent(contexts.simon.restContext, contentObj.id, (err, contentObj) => { + assert.ok(!err); - // Route and deliver activities - ActivityTestsUtil.collectAndGetActivityStream( - contexts.simon.restContext, - null, - null, - (err, activities) => { - assert.ok(!err); - - // Register for some streams - const data = { - authentication: { - userId: contexts.simon.user.id, - tenantAlias: simonFull.tenant.alias, - signature: simonFull.signature + // Route and deliver activities + ActivityTestsUtil.collectAndGetActivityStream( + contexts.simon.restContext, + null, + null, + (err, activities) => { + assert.ok(!err); + + // Register for some streams + const data = { + authentication: { + userId: contexts.simon.user.id, + tenantAlias: simonFull.tenant.alias, + signature: simonFull.signature + }, + streams: [ + { + resourceId: contentObj.id, + streamType: 'activity', + token: contentObj.signature }, - streams: [ - { - resourceId: contentObj.id, - streamType: 'activity', - token: contentObj.signature - }, - { - resourceId: contentObj.id, - streamType: 'message', - token: contentObj.signature - } - ] - }; - - ActivityTestsUtil.getFullySetupPushClient(data, client => { - callback(contexts, contentObj, client); - }); - } - ); - } - ); + { + resourceId: contentObj.id, + streamType: 'message', + token: contentObj.signature + } + ] + }; + + ActivityTestsUtil.getFullySetupPushClient(data, client => { + callback(contexts, contentObj, client); + }); + } + ); + }); } ); }); @@ -285,14 +260,9 @@ describe('Content Push', () => { it('verify content visibility updates trigger a push notification', callback => { setupFixture((contexts, contentObj, client) => { // Trigger an update - RestAPI.Content.updateContent( - contexts.branden.restContext, - contentObj.id, - { visibility: 'loggedin' }, - err => { - assert.ok(!err); - } - ); + RestAPI.Content.updateContent(contexts.branden.restContext, contentObj.id, { visibility: 'loggedin' }, err => { + assert.ok(!err); + }); ActivityTestsUtil.waitForPushActivity( client, @@ -316,14 +286,9 @@ describe('Content Push', () => { it('verify a new revision triggers a push notification', callback => { setupFixture((contexts, contentObj, client) => { // Upload a new revision - RestAPI.Content.updateFileBody( - contexts.branden.restContext, - contentObj.id, - getFileStream, - err => { - assert.ok(!err); - } - ); + RestAPI.Content.updateFileBody(contexts.branden.restContext, contentObj.id, getFileStream, err => { + assert.ok(!err); + }); ActivityTestsUtil.waitForPushActivity( client, @@ -334,15 +299,11 @@ describe('Content Push', () => { null, activity => { // Verify we have the latest revision id available for reloading of any links/images - RestAPI.Content.getContent( - contexts.branden.restContext, - contentObj.id, - (err, contentObj) => { - assert.ok(!err); - assert.strictEqual(activity.object.latestRevisionId, contentObj.latestRevisionId); - return client.close(callback); - } - ); + RestAPI.Content.getContent(contexts.branden.restContext, contentObj.id, (err, contentObj) => { + assert.ok(!err); + assert.strictEqual(activity.object.latestRevisionId, contentObj.latestRevisionId); + return client.close(callback); + }); } ); }); @@ -356,48 +317,36 @@ describe('Content Push', () => { const initialRevisionId = contentObj.latestRevisionId; // Upload a new revision - RestAPI.Content.updateFileBody( - contexts.branden.restContext, - contentObj.id, - getFileStream, - err => { - assert.ok(!err); + RestAPI.Content.updateFileBody(contexts.branden.restContext, contentObj.id, getFileStream, err => { + assert.ok(!err); - // Restore the previous revision - RestAPI.Content.restoreRevision( - contexts.branden.restContext, - contentObj.id, - initialRevisionId, - (err, revisionObj) => { - assert.ok(!err); - } - ); + // Restore the previous revision + RestAPI.Content.restoreRevision( + contexts.branden.restContext, + contentObj.id, + initialRevisionId, + (err, revisionObj) => { + assert.ok(!err); + } + ); - ActivityTestsUtil.waitForPushActivity( - client, - ContentConstants.activity.ACTIVITY_CONTENT_RESTORED_REVISION, - ActivityConstants.verbs.UPDATE, - contexts.branden.user.id, - contentObj.id, - null, - activity => { - // Verify we have the latest revision id available for reloading of any links/images - RestAPI.Content.getContent( - contexts.branden.restContext, - contentObj.id, - (err, contentObj) => { - assert.ok(!err); - assert.strictEqual( - activity.object.latestRevisionId, - contentObj.latestRevisionId - ); - return client.close(callback); - } - ); - } - ); - } - ); + ActivityTestsUtil.waitForPushActivity( + client, + ContentConstants.activity.ACTIVITY_CONTENT_RESTORED_REVISION, + ActivityConstants.verbs.UPDATE, + contexts.branden.user.id, + contentObj.id, + null, + activity => { + // Verify we have the latest revision id available for reloading of any links/images + RestAPI.Content.getContent(contexts.branden.restContext, contentObj.id, (err, contentObj) => { + assert.ok(!err); + assert.strictEqual(activity.object.latestRevisionId, contentObj.latestRevisionId); + return client.close(callback); + }); + } + ); + }); }); }); @@ -410,8 +359,8 @@ describe('Content Push', () => { let activity = null; /*! - * Perform the assertions between the activity and comment and finish the test - */ + * Perform the assertions between the activity and comment and finish the test + */ const _assertAndCallback = _.after(2, () => { // Verify that we have access to the message body and createdBy property assert.strictEqual(activity.object[ActivityConstants.properties.OAE_ID], comment.id); @@ -464,8 +413,8 @@ describe('Content Push', () => { let activity = null; /*! - * Perform the assertions between the activity and comment and finish the test - */ + * Perform the assertions between the activity and comment and finish the test + */ const _assertAndCallback = _.after(2, () => { // Verify that we have access to the message body and createdBy property assert.strictEqual(activity.object[ActivityConstants.properties.OAE_ID], comment.id); diff --git a/packages/oae-content/tests/test-search.js b/packages/oae-content/tests/test-search.js index 3fc8f932c0..db3abd710d 100644 --- a/packages/oae-content/tests/test-search.js +++ b/packages/oae-content/tests/test-search.js @@ -12,25 +12,22 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const _ = require('underscore'); - -const Cassandra = require('oae-util/lib/cassandra'); -const ConfigTestUtil = require('oae-config/lib/test/util'); -const ElasticSearch = require('oae-search/lib/internal/elasticsearch'); -const MQ = require('oae-util/lib/mq'); -const MQTestUtil = require('oae-util/lib/test/mq-util'); -const PreviewAPI = require('oae-preview-processor/lib/api'); -const PreviewConstants = require('oae-preview-processor/lib/constants'); -const PreviewTestUtil = require('oae-preview-processor/lib/test/util'); -const RestAPI = require('oae-rest'); -const SearchAPI = require('oae-search'); -const { SearchConstants } = require('oae-search/lib/constants'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TaskQueue = require('oae-util/lib/taskqueue'); -const TestsUtil = require('oae-tests'); +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import _ from 'underscore'; + +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as ElasticSearch from 'oae-search/lib/internal/elasticsearch'; +import * as MQTestUtil from 'oae-util/lib/test/mq-util'; +import * as PreviewAPI from 'oae-preview-processor/lib/api'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; +import * as PreviewTestUtil from 'oae-preview-processor/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as SearchAPI from 'oae-search'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests'; describe('Search', () => { // REST contexts we can use to do REST requests @@ -44,9 +41,7 @@ describe('Search', () => { anonymousRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); camAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.cam.host); gtAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.gt.host); - signedAdminRestContext = TestsUtil.createTenantAdminRestContext( - global.oaeTests.tenants.localhost.host - ); + signedAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.localhost.host); globalAdminRestContext = TestsUtil.createGlobalAdminRestContext(); return callback(); }); @@ -89,12 +84,12 @@ describe('Search', () => { }; /*! - * Get the document with the specified id from the search results. - * - * @param {SearchResult} results The search results object - * @param {String} docId The id of the document to search - * @return {Object} The search document. `null` if it didn't exist - */ + * Get the document with the specified id from the search results. + * + * @param {SearchResult} results The search results object + * @param {String} docId The id of the document to search + * @return {Object} The search document. `null` if it didn't exist + */ const _getDocById = function(results, docId) { for (let i = 0; i < results.results.length; i++) { const doc = results.results[i]; @@ -102,15 +97,16 @@ describe('Search', () => { return doc; } } + return null; }; /*! - * Creates a file and waits till it has been preview processed. - * - * @param {Stream} stream The stream that points to the file that should be uploaded. - * @param {Function} callback Standard callback method that gets called when the file has previews associated to it. - */ + * Creates a file and waits till it has been preview processed. + * + * @param {Stream} stream The stream that points to the file that should be uploaded. + * @param {Function} callback Standard callback method that gets called when the file has previews associated to it. + */ const _createContentAndWait = function(stream, callback) { // When the queue is empty, we create a piece of content for which we can generate preview items. MQTestUtil.whenTasksEmpty(PreviewConstants.MQ.TASK_GENERATE_PREVIEWS, () => { @@ -132,18 +128,14 @@ describe('Search', () => { // Wait till the PP items have been generated MQTestUtil.whenTasksEmpty(PreviewConstants.MQ.TASK_GENERATE_PREVIEWS, () => { // Ensure the preview items are there - RestAPI.Content.getContent( - creator.restContext, - contentObj.id, - (err, updatedContent) => { - assert.ok(!err); - assert.ok(updatedContent.previews); - assert.strictEqual(updatedContent.previews.status, 'done'); - assert.strictEqual(updatedContent.previews.pageCount, 1); + RestAPI.Content.getContent(creator.restContext, contentObj.id, (err, updatedContent) => { + assert.ok(!err); + assert.ok(updatedContent.previews); + assert.strictEqual(updatedContent.previews.status, 'done'); + assert.strictEqual(updatedContent.previews.pageCount, 1); - return callback(creator, updatedContent); - } - ); + return callback(creator, updatedContent); + }); }); } ); @@ -199,33 +191,28 @@ describe('Search', () => { assert.ok(!contentDoc); // Fire off an indexing task using just the content id - SearchAPI.postIndexTask( - 'content', - [{ id: link.id }], - { resource: true }, - err => { - assert.ok(!err); - - // Ensure that the full content item is now back in the search index - SearchTestsUtil.searchAll( - doer.restContext, - 'general', - null, - { resourceTypes: 'content', q: 'index-without-full-content-item' }, - (err, results) => { - assert.ok(!err); - const contentDoc = _getDocById(results, link.id); - assert.ok(contentDoc); - - // Ensure that the full tenant object is passed back. - assert.ok(_.isObject(contentDoc.tenant)); - assert.ok(contentDoc.tenant.displayName); - assert.ok(contentDoc.tenant.alias); - return callback(); - } - ); - } - ); + SearchAPI.postIndexTask('content', [{ id: link.id }], { resource: true }, err => { + assert.ok(!err); + + // Ensure that the full content item is now back in the search index + SearchTestsUtil.searchAll( + doer.restContext, + 'general', + null, + { resourceTypes: 'content', q: 'index-without-full-content-item' }, + (err, results) => { + assert.ok(!err); + const contentDoc = _getDocById(results, link.id); + assert.ok(contentDoc); + + // Ensure that the full tenant object is passed back. + assert.ok(_.isObject(contentDoc.tenant)); + assert.ok(contentDoc.tenant.displayName); + assert.ok(contentDoc.tenant.alias); + return callback(); + } + ); + }); } ); }); @@ -327,6 +314,7 @@ describe('Search', () => { const pdfStream = function() { return fs.createReadStream(path.join(__dirname, '/data/test.pdf')); }; + _createContentAndWait(pdfStream, (creator, content) => { // Verify we can find the content from the PDF in general searches SearchTestsUtil.searchAll( @@ -377,6 +365,7 @@ describe('Search', () => { const pdfStream = function() { return fs.createReadStream(path.join(__dirname, '/data/test.pdf')); }; + _createContentAndWait(pdfStream, (creator, content) => { // Verify we can find the content from the PDF in general searches SearchTestsUtil.searchAll( diff --git a/packages/oae-content/tests/test-tenant-separation.js b/packages/oae-content/tests/test-tenant-separation.js index 5df89e5f44..14c348a890 100644 --- a/packages/oae-content/tests/test-tenant-separation.js +++ b/packages/oae-content/tests/test-tenant-separation.js @@ -13,17 +13,17 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const util = require('util'); -const _ = require('underscore'); +import assert from 'assert'; +import util from 'util'; +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const ConfigTestUtil = require('oae-config/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const TestsUtil = require('oae-tests'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as ContentTestUtil from 'oae-content/lib/test/util'; -const ContentTestUtil = require('oae-content/lib/test/util'); +import { RestContext } from 'oae-rest/lib/model'; describe('Content', () => { // Rest context that can be used every time we need to make a request as an anonymous user diff --git a/packages/oae-context/lib/api.js b/packages/oae-context/lib/api.js index 419aee2453..fdf17cc163 100644 --- a/packages/oae-context/lib/api.js +++ b/packages/oae-context/lib/api.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const TenantAPI = require('oae-tenants'); +import * as TenantAPI from 'oae-tenants'; /** * A generic context object that represents a user execution context. @@ -83,11 +83,13 @@ const Context = function(tenant, user, authenticationStrategy, locale, imposter) // 2. Check if there is a locale in the request context (headers) } + if (that.locale()) { return that.locale(); // 3. Otherwise we'll need to fall back to the `default` key } + return 'default'; }; @@ -114,6 +116,4 @@ Context.fromUser = function(user) { return new Context(TenantAPI.getTenant(user.tenant.alias), user); }; -module.exports = { - Context -}; +export { Context }; diff --git a/packages/oae-context/tests/test-context.js b/packages/oae-context/tests/test-context.js index 6a2fd01faf..eeccb492af 100644 --- a/packages/oae-context/tests/test-context.js +++ b/packages/oae-context/tests/test-context.js @@ -13,31 +13,20 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const { Locale } = require('locale'); +import assert from 'assert'; +import { Locale } from 'locale'; -const { User } = require('oae-principals/lib/model'); -const { Tenant } = require('oae-tenants/lib/model'); +import { User } from 'oae-principals/lib/model'; -const { Context } = require('oae-context'); +import { Context } from 'oae-context'; describe('Context', () => { /** * Test that verifies a simple context */ it('verify simple context', callback => { - const user = new User( - global.oaeTests.tenants.cam.alias, - 'u:camtest:physx', - 'physx', - 'bert@apereo.org' - ); - const imposter = new User( - global.oaeTests.tenants.cam.alias, - 'u:camtest:simong', - 'simong', - 'simon@apereo.org' - ); + const user = new User(global.oaeTests.tenants.cam.alias, 'u:camtest:physx', 'physx', 'bert@apereo.org'); + const imposter = new User(global.oaeTests.tenants.cam.alias, 'u:camtest:simong', 'simong', 'simon@apereo.org'); const ctx = new Context(global.oaeTests.tenants.cam, user, 'twitter', null, imposter); assert.deepStrictEqual(ctx.tenant(), global.oaeTests.tenants.cam); assert.deepStrictEqual(ctx.user(), user); @@ -51,12 +40,7 @@ describe('Context', () => { * Test that verifies the locale setter can handle defaulted locales */ it('verify the locale setter can handle defaulted locales', callback => { - const user = new User( - global.oaeTests.tenants.cam.alias, - 'u:camtest:physx', - 'physx', - 'bert@apereo.org' - ); + const user = new User(global.oaeTests.tenants.cam.alias, 'u:camtest:physx', 'physx', 'bert@apereo.org'); const ctx = new Context(global.oaeTests.tenants.cam, user, 'twitter', 'en_UK'); assert.deepStrictEqual(ctx.tenant(), global.oaeTests.tenants.cam); assert.deepStrictEqual(ctx.user(), user); diff --git a/packages/oae-discussions/config/discussion.js b/packages/oae-discussions/config/discussion.js index c83b30d480..d600764e10 100644 --- a/packages/oae-discussions/config/discussion.js +++ b/packages/oae-discussions/config/discussion.js @@ -13,28 +13,26 @@ * permissions and limitations under the License. */ -var Fields = require('oae-config/lib/fields'); +import * as Fields from 'oae-config/lib/fields'; -module.exports = { - 'title': 'OAE Discussions Module', - 'visibility': { - 'name': 'Default Visibility Values', - 'description': 'Default visibility setting for new discussions', - 'elements': { - 'discussion': new Fields.List('Discussion Visibility', 'Default visibility for a new discussion', 'public', [ - { - 'name': 'Public', - 'value': 'public' - }, - { - 'name': 'Authenticated Users', - 'value': 'loggedin' - }, - { - 'name': 'Private', - 'value': 'private' - } - ]) - } - } +export const title = 'OAE Discussions Module'; +export const visibility = { + name: 'Default Visibility Values', + description: 'Default visibility setting for new discussions', + elements: { + discussion: new Fields.List('Discussion Visibility', 'Default visibility for a new discussion', 'public', [ + { + name: 'Public', + value: 'public' + }, + { + name: 'Authenticated Users', + value: 'loggedin' + }, + { + name: 'Private', + value: 'private' + } + ]) + } }; diff --git a/packages/oae-discussions/lib/activity.js b/packages/oae-discussions/lib/activity.js index 821e713dad..78062b95ee 100644 --- a/packages/oae-discussions/lib/activity.js +++ b/packages/oae-discussions/lib/activity.js @@ -13,22 +13,22 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); - -const ActivityAPI = require('oae-activity'); -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityModel = require('oae-activity/lib/model'); -const ActivityUtil = require('oae-activity/lib/util'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzUtil = require('oae-authz/lib/util'); -const MessageBoxAPI = require('oae-messagebox'); -const MessageBoxUtil = require('oae-messagebox/lib/util'); -const PrincipalsUtil = require('oae-principals/lib/util'); -const TenantsUtil = require('oae-tenants/lib/util'); - -const DiscussionsAPI = require('./api'); -const { DiscussionsConstants } = require('./constants'); -const DiscussionsDAO = require('./internal/dao'); +import _ from 'underscore'; + +import * as ActivityAPI from 'oae-activity'; +import * as ActivityModel from 'oae-activity/lib/model'; +import * as ActivityUtil from 'oae-activity/lib/util'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as MessageBoxAPI from 'oae-messagebox'; +import * as MessageBoxUtil from 'oae-messagebox/lib/util'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; +import * as TenantsUtil from 'oae-tenants/lib/util'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as DiscussionsDAO from './internal/dao'; +import DiscussionsAPI from './api'; + +import { DiscussionsConstants } from './constants'; /// //////////////////// // DISCUSSION-CREATE // @@ -102,58 +102,52 @@ ActivityAPI.registerActivityType(DiscussionsConstants.activity.ACTIVITY_DISCUSSI } }); -ActivityAPI.registerActivityType( - DiscussionsConstants.activity.ACTIVITY_DISCUSSION_UPDATE_VISIBILITY, - { - streams: { - activity: { - router: { - actor: ['self'], - object: ['self', 'members'] - } - }, - notification: { - router: { - object: ['managers'] - } +ActivityAPI.registerActivityType(DiscussionsConstants.activity.ACTIVITY_DISCUSSION_UPDATE_VISIBILITY, { + streams: { + activity: { + router: { + actor: ['self'], + object: ['self', 'members'] + } + }, + notification: { + router: { + object: ['managers'] } } } -); +}); /*! * Post either a discussion-update or discussion-update-visibility activity when a user updates a discussion's metadata. */ -DiscussionsAPI.on( - DiscussionsConstants.events.UPDATED_DISCUSSION, - (ctx, newDiscussion, oldDiscussion) => { - const millis = Date.now(); - const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { - user: ctx.user() - }); - const objectResource = new ActivityModel.ActivitySeedResource('discussion', newDiscussion.id, { - discussion: newDiscussion - }); - - // We discriminate between general updates and visibility changes. - // If the visibility has changed, we fire a visibility changed activity *instead* of an update activity - let activityType = null; - if (newDiscussion.visibility === oldDiscussion.visibility) { - activityType = DiscussionsConstants.activity.ACTIVITY_DISCUSSION_UPDATE; - } else { - activityType = DiscussionsConstants.activity.ACTIVITY_DISCUSSION_UPDATE_VISIBILITY; - } +DiscussionsAPI.on(DiscussionsConstants.events.UPDATED_DISCUSSION, (ctx, newDiscussion, oldDiscussion) => { + const millis = Date.now(); + const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { + user: ctx.user() + }); + const objectResource = new ActivityModel.ActivitySeedResource('discussion', newDiscussion.id, { + discussion: newDiscussion + }); - const activitySeed = new ActivityModel.ActivitySeed( - activityType, - millis, - ActivityConstants.verbs.UPDATE, - actorResource, - objectResource - ); - ActivityAPI.postActivity(ctx, activitySeed); + // We discriminate between general updates and visibility changes. + // If the visibility has changed, we fire a visibility changed activity *instead* of an update activity + let activityType = null; + if (newDiscussion.visibility === oldDiscussion.visibility) { + activityType = DiscussionsConstants.activity.ACTIVITY_DISCUSSION_UPDATE; + } else { + activityType = DiscussionsConstants.activity.ACTIVITY_DISCUSSION_UPDATE_VISIBILITY; } -); + + const activitySeed = new ActivityModel.ActivitySeed( + activityType, + millis, + ActivityConstants.verbs.UPDATE, + actorResource, + objectResource + ); + ActivityAPI.postActivity(ctx, activitySeed); +}); /// ///////////////////// // DISCUSSION-MESSAGE // @@ -191,32 +185,28 @@ ActivityAPI.registerActivityType(DiscussionsConstants.activity.ACTIVITY_DISCUSSI /*! * Post a discussion-message activity when a user comments on a discussion */ -DiscussionsAPI.on( - DiscussionsConstants.events.CREATED_DISCUSSION_MESSAGE, - (ctx, message, discussion) => { - const millis = Date.now(); - const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { - user: ctx.user() - }); - const objectResource = new ActivityModel.ActivitySeedResource( - 'discussion-message', - message.id, - { discussionId: discussion.id, message } - ); - const targetResource = new ActivityModel.ActivitySeedResource('discussion', discussion.id, { - discussion - }); - const activitySeed = new ActivityModel.ActivitySeed( - DiscussionsConstants.activity.ACTIVITY_DISCUSSION_MESSAGE, - millis, - ActivityConstants.verbs.POST, - actorResource, - objectResource, - targetResource - ); - ActivityAPI.postActivity(ctx, activitySeed); - } -); +DiscussionsAPI.on(DiscussionsConstants.events.CREATED_DISCUSSION_MESSAGE, (ctx, message, discussion) => { + const millis = Date.now(); + const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { + user: ctx.user() + }); + const objectResource = new ActivityModel.ActivitySeedResource('discussion-message', message.id, { + discussionId: discussion.id, + message + }); + const targetResource = new ActivityModel.ActivitySeedResource('discussion', discussion.id, { + discussion + }); + const activitySeed = new ActivityModel.ActivitySeed( + DiscussionsConstants.activity.ACTIVITY_DISCUSSION_MESSAGE, + millis, + ActivityConstants.verbs.POST, + actorResource, + objectResource, + targetResource + ); + ActivityAPI.postActivity(ctx, activitySeed); +}); /// //////////////////////////////////////////////////////////////////////////////// // DISCUSSION-SHARE, DISCUSSION-ADD-TO-LIBRARY and DISCUSSION-UPDATE-MEMBER-ROLE // @@ -265,101 +255,89 @@ ActivityAPI.registerActivityType(DiscussionsConstants.activity.ACTIVITY_DISCUSSI } }); -ActivityAPI.registerActivityType( - DiscussionsConstants.activity.ACTIVITY_DISCUSSION_UPDATE_MEMBER_ROLE, - { - groupBy: [{ actor: true, target: true }], - streams: { - activity: { - router: { - actor: ['self'], - object: ['self', 'members'], - target: ['managers'] - } +ActivityAPI.registerActivityType(DiscussionsConstants.activity.ACTIVITY_DISCUSSION_UPDATE_MEMBER_ROLE, { + groupBy: [{ actor: true, target: true }], + streams: { + activity: { + router: { + actor: ['self'], + object: ['self', 'members'], + target: ['managers'] } } } -); +}); /*! * Post a discussion-share or discussion-add-to-library activity based on discussion sharing */ -DiscussionsAPI.on( - DiscussionsConstants.events.UPDATED_DISCUSSION_MEMBERS, - (ctx, discussion, memberChangeInfo, opts) => { - if (opts.invitation) { - // If this member update came from an invitation, we bypass adding activity as there is a - // dedicated activity for that - return; - } - - const addedPrincipalIds = _.pluck(memberChangeInfo.members.added, 'id'); - const updatedPrincipalIds = _.pluck(memberChangeInfo.members.updated, 'id'); +DiscussionsAPI.on(DiscussionsConstants.events.UPDATED_DISCUSSION_MEMBERS, (ctx, discussion, memberChangeInfo, opts) => { + if (opts.invitation) { + // If this member update came from an invitation, we bypass adding activity as there is a + // dedicated activity for that + return; + } - const millis = Date.now(); - const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { - user: ctx.user() - }); - const discussionResource = new ActivityModel.ActivitySeedResource('discussion', discussion.id, { - discussion - }); + const addedPrincipalIds = _.pluck(memberChangeInfo.members.added, 'id'); + const updatedPrincipalIds = _.pluck(memberChangeInfo.members.updated, 'id'); - // For users that are newly added to the discussion, post either a share or "add to library" activity, depending on context - _.each(addedPrincipalIds, principalId => { - if (principalId === ctx.user().id) { - // Users can't "share" with themselves, they actually "add it to their library" - ActivityAPI.postActivity( - ctx, - new ActivityModel.ActivitySeed( - DiscussionsConstants.activity.ACTIVITY_DISCUSSION_ADD_TO_LIBRARY, - millis, - ActivityConstants.verbs.ADD, - actorResource, - discussionResource - ) - ); - } else { - // A user shared discussion with some other user, fire the discussion share activity - const principalResourceType = PrincipalsUtil.isGroup(principalId) ? 'group' : 'user'; - const principalResource = new ActivityModel.ActivitySeedResource( - principalResourceType, - principalId - ); - ActivityAPI.postActivity( - ctx, - new ActivityModel.ActivitySeed( - DiscussionsConstants.activity.ACTIVITY_DISCUSSION_SHARE, - millis, - ActivityConstants.verbs.SHARE, - actorResource, - discussionResource, - principalResource - ) - ); - } - }); + const millis = Date.now(); + const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { + user: ctx.user() + }); + const discussionResource = new ActivityModel.ActivitySeedResource('discussion', discussion.id, { + discussion + }); - // For users whose role changed, post the discussion-update-member-role activity - _.each(updatedPrincipalIds, principalId => { - const principalResourceType = PrincipalsUtil.isGroup(principalId) ? 'group' : 'user'; - const principalResource = new ActivityModel.ActivitySeedResource( - principalResourceType, - principalId - ); + // For users that are newly added to the discussion, post either a share or "add to library" activity, depending on context + _.each(addedPrincipalIds, principalId => { + if (principalId === ctx.user().id) { + // Users can't "share" with themselves, they actually "add it to their library" ActivityAPI.postActivity( ctx, new ActivityModel.ActivitySeed( - DiscussionsConstants.activity.ACTIVITY_DISCUSSION_UPDATE_MEMBER_ROLE, + DiscussionsConstants.activity.ACTIVITY_DISCUSSION_ADD_TO_LIBRARY, millis, - ActivityConstants.verbs.UPDATE, + ActivityConstants.verbs.ADD, actorResource, - principalResource, discussionResource ) ); - }); - } -); + } else { + // A user shared discussion with some other user, fire the discussion share activity + const principalResourceType = PrincipalsUtil.isGroup(principalId) ? 'group' : 'user'; + const principalResource = new ActivityModel.ActivitySeedResource(principalResourceType, principalId); + ActivityAPI.postActivity( + ctx, + new ActivityModel.ActivitySeed( + DiscussionsConstants.activity.ACTIVITY_DISCUSSION_SHARE, + millis, + ActivityConstants.verbs.SHARE, + actorResource, + discussionResource, + principalResource + ) + ); + } + }); + + // For users whose role changed, post the discussion-update-member-role activity + _.each(updatedPrincipalIds, principalId => { + const principalResourceType = PrincipalsUtil.isGroup(principalId) ? 'group' : 'user'; + const principalResource = new ActivityModel.ActivitySeedResource(principalResourceType, principalId); + ActivityAPI.postActivity( + ctx, + new ActivityModel.ActivitySeed( + DiscussionsConstants.activity.ACTIVITY_DISCUSSION_UPDATE_MEMBER_ROLE, + millis, + ActivityConstants.verbs.UPDATE, + actorResource, + principalResource, + discussionResource + ) + ); + }); +}); /// //////////////////////// // ACTIVITY ENTITY TYPES // @@ -371,9 +349,7 @@ DiscussionsAPI.on( */ const _discussionProducer = function(resource, callback) { const discussion = - resource.resourceData && resource.resourceData.discussion - ? resource.resourceData.discussion - : null; + resource.resourceData && resource.resourceData.discussion ? resource.resourceData.discussion : null; // If the discussion item was fired with the resource, use it instead of fetching if (discussion) { @@ -438,9 +414,7 @@ const _discussionTransformer = function(ctx, activityEntities, callback) { transformedActivityEntities[activityId] = transformedActivityEntities[activityId] || {}; _.each(entities, (entity, entityId) => { // Transform the persistent entity into an ActivityStrea.ms compliant format - transformedActivityEntities[activityId][ - entityId - ] = _transformPersistentDiscussionActivityEntity(ctx, entity); + transformedActivityEntities[activityId][entityId] = _transformPersistentDiscussionActivityEntity(ctx, entity); }); }); return callback(null, transformedActivityEntities); @@ -506,9 +480,7 @@ const _discussionMessageTransformer = function(ctx, activityEntities, callback) const resource = AuthzUtil.getResourceFromId(discussionId); const profilePath = '/discussion/' + resource.tenantAlias + '/' + resource.resourceId; const urlFormat = '/api/discussion/' + discussionId + '/messages/%s'; - transformedActivityEntities[activityId][ - entityId - ] = MessageBoxUtil.transformPersistentMessageActivityEntity( + transformedActivityEntities[activityId][entityId] = MessageBoxUtil.transformPersistentMessageActivityEntity( ctx, entity, profilePath, @@ -544,11 +516,7 @@ ActivityAPI.registerActivityEntityType('discussion', { internal: _discussionInternalTransformer }, propagation(associationsCtx, entity, callback) { - ActivityUtil.getStandardResourcePropagation( - entity.discussion.visibility, - AuthzConstants.joinable.NO, - callback - ); + ActivityUtil.getStandardResourcePropagation(entity.discussion.visibility, AuthzConstants.joinable.NO, callback); } }); @@ -570,58 +538,42 @@ ActivityAPI.registerActivityEntityType('discussion-message', { /*! * Register an association that presents the discussion */ -ActivityAPI.registerActivityEntityAssociation( - 'discussion', - 'self', - (associationsCtx, entity, callback) => { - return callback(null, [entity[ActivityConstants.properties.OAE_ID]]); - } -); +ActivityAPI.registerActivityEntityAssociation('discussion', 'self', (associationsCtx, entity, callback) => { + return callback(null, [entity[ActivityConstants.properties.OAE_ID]]); +}); /*! * Register an association that presents the members of a discussion categorized by role */ -ActivityAPI.registerActivityEntityAssociation( - 'discussion', - 'members-by-role', - (associationsCtx, entity, callback) => { - ActivityUtil.getAllAuthzMembersByRole(entity[ActivityConstants.properties.OAE_ID], callback); - } -); +ActivityAPI.registerActivityEntityAssociation('discussion', 'members-by-role', (associationsCtx, entity, callback) => { + ActivityUtil.getAllAuthzMembersByRole(entity[ActivityConstants.properties.OAE_ID], callback); +}); /*! * Register an association that presents all the indirect members of a discussion */ -ActivityAPI.registerActivityEntityAssociation( - 'discussion', - 'members', - (associationsCtx, entity, callback) => { - associationsCtx.get('members-by-role', (err, membersByRole) => { - if (err) { - return callback(err); - } +ActivityAPI.registerActivityEntityAssociation('discussion', 'members', (associationsCtx, entity, callback) => { + associationsCtx.get('members-by-role', (err, membersByRole) => { + if (err) { + return callback(err); + } - return callback(null, _.flatten(_.values(membersByRole))); - }); - } -); + return callback(null, _.flatten(_.values(membersByRole))); + }); +}); /*! * Register an association that presents all the managers of a discussion */ -ActivityAPI.registerActivityEntityAssociation( - 'discussion', - 'managers', - (associationsCtx, entity, callback) => { - associationsCtx.get('members-by-role', (err, membersByRole) => { - if (err) { - return callback(err); - } +ActivityAPI.registerActivityEntityAssociation('discussion', 'managers', (associationsCtx, entity, callback) => { + associationsCtx.get('members-by-role', (err, membersByRole) => { + if (err) { + return callback(err); + } - return callback(null, membersByRole[AuthzConstants.role.MANAGER]); - }); - } -); + return callback(null, membersByRole[AuthzConstants.role.MANAGER]); + }); +}); /*! * Register an assocation that presents all the commenting contributors of a discussion @@ -630,22 +582,13 @@ ActivityAPI.registerActivityEntityAssociation( 'discussion', 'message-contributors', (associationsCtx, entity, callback) => { - MessageBoxAPI.getRecentContributions( - entity[ActivityConstants.properties.OAE_ID], - null, - 100, - callback - ); + MessageBoxAPI.getRecentContributions(entity[ActivityConstants.properties.OAE_ID], null, 100, callback); } ); /*! * Register an association that presents the discussion for a discussion-message entity */ -ActivityAPI.registerActivityEntityAssociation( - 'discussion-message', - 'self', - (associationsCtx, entity, callback) => { - return callback(null, [entity.discussionId]); - } -); +ActivityAPI.registerActivityEntityAssociation('discussion-message', 'self', (associationsCtx, entity, callback) => { + return callback(null, [entity.discussionId]); +}); diff --git a/packages/oae-discussions/lib/api.discussions.js b/packages/oae-discussions/lib/api.discussions.js index 52b4b63a71..1ccefc8e4f 100644 --- a/packages/oae-discussions/lib/api.discussions.js +++ b/packages/oae-discussions/lib/api.discussions.js @@ -14,27 +14,31 @@ */ /* eslint-disable no-unused-vars */ -const _ = require('underscore'); - -const AuthzAPI = require('oae-authz'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzInvitations = require('oae-authz/lib/invitations'); -const AuthzPermissions = require('oae-authz/lib/permissions'); -const LibraryAPI = require('oae-library'); -const log = require('oae-logger').logger('discussions-api'); -const MessageBoxAPI = require('oae-messagebox'); -const { MessageBoxConstants } = require('oae-messagebox/lib/constants'); -const OaeUtil = require('oae-util/lib/util'); -const PrincipalsUtil = require('oae-principals/lib/util'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const ResourceActions = require('oae-resource/lib/actions'); -const Signature = require('oae-util/lib/signature'); -const { Validator } = require('oae-authz/lib/validator'); - -const DiscussionsAPI = require('oae-discussions'); -const DiscussionsConfig = require('oae-config').config('oae-discussions'); -const { DiscussionsConstants } = require('./constants'); -const DiscussionsDAO = require('./internal/dao'); +import _ from 'underscore'; +import DiscussionsAPI from 'oae-discussions'; + +import * as AuthzAPI from 'oae-authz'; +import * as AuthzInvitations from 'oae-authz/lib/invitations'; +import * as AuthzPermissions from 'oae-authz/lib/permissions'; +import * as LibraryAPI from 'oae-library'; +import { logger } from 'oae-logger'; +import * as MessageBoxAPI from 'oae-messagebox'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as ResourceActions from 'oae-resource/lib/actions'; +import * as Signature from 'oae-util/lib/signature'; +import { setUpConfig } from 'oae-config'; + +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { MessageBoxConstants } from 'oae-messagebox/lib/constants'; +import { Validator } from 'oae-authz/lib/validator'; +import * as DiscussionsDAO from './internal/dao'; +import { DiscussionsConstants } from './constants'; + +const log = logger('discussions-api'); + +const DiscussionsConfig = setUpConfig('oae-discussions'); // Discussion fields that are allowed to be updated const DISCUSSION_UPDATE_FIELDS = ['displayName', 'description', 'visibility']; @@ -52,17 +56,8 @@ const DISCUSSION_UPDATE_FIELDS = ['displayName', 'description', 'visibility']; * @param {Object} callback.err An error that occurred, if any * @param {Discussion} callback.discussion The discussion object that was created */ -const createDiscussion = function( - ctx, - displayName, - description, - visibility, - roles, - opts, - callback -) { - visibility = - visibility || DiscussionsConfig.getValue(ctx.tenant().alias, 'visibility', 'discussion'); +const createDiscussion = function(ctx, displayName, description, visibility, roles, opts, callback) { + visibility = visibility || DiscussionsConfig.getValue(ctx.tenant().alias, 'visibility', 'discussion'); roles = roles || {}; opts = opts || {}; @@ -70,27 +65,19 @@ const createDiscussion = function( // Verify basic properties const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'Anonymous users cannot create a discussion' }) - .isLoggedInUser(ctx); - validator - .check(displayName, { code: 400, msg: 'Must provide a display name for the discussion' }) - .notEmpty(); + validator.check(null, { code: 401, msg: 'Anonymous users cannot create a discussion' }).isLoggedInUser(ctx); + validator.check(displayName, { code: 400, msg: 'Must provide a display name for the discussion' }).notEmpty(); validator .check(displayName, { code: 400, msg: 'A display name can be at most 1000 characters long' }) .isShortString(); - validator - .check(description, { code: 400, msg: 'Must provide a description for the discussion' }) - .notEmpty(); + validator.check(description, { code: 400, msg: 'Must provide a description for the discussion' }).notEmpty(); validator .check(description, { code: 400, msg: 'A description can be at most 10000 characters long' }) .isMediumString(); validator .check(visibility, { code: 400, - msg: - 'An invalid discussion visibility option has been provided. Must be one of: ' + - allVisibilities.join(', ') + msg: 'An invalid discussion visibility option has been provided. Must be one of: ' + allVisibilities.join(', ') }) .isIn(allVisibilities); @@ -124,19 +111,13 @@ const createDiscussion = function( return callback(err); } - DiscussionsAPI.emit( - DiscussionsConstants.events.CREATED_DISCUSSION, - ctx, - discussion, - memberChangeInfo, - errs => { - if (errs) { - return callback(_.first(errs)); - } - - return callback(null, discussion); + DiscussionsAPI.emit(DiscussionsConstants.events.CREATED_DISCUSSION, ctx, discussion, memberChangeInfo, errs => { + if (errs) { + return callback(_.first(errs)); } - ); + + return callback(null, discussion); + }); }); }; @@ -154,12 +135,8 @@ const updateDiscussion = function(ctx, discussionId, profileFields, callback) { const allVisibilities = _.values(AuthzConstants.visibility); const validator = new Validator(); - validator - .check(discussionId, { code: 400, msg: 'A discussion id must be provided' }) - .isResourceId(); - validator - .check(null, { code: 401, msg: 'You must be authenticated to update a discussion' }) - .isLoggedInUser(ctx); + validator.check(discussionId, { code: 400, msg: 'A discussion id must be provided' }).isResourceId(); + validator.check(null, { code: 401, msg: 'You must be authenticated to update a discussion' }).isLoggedInUser(ctx); validator .check(_.keys(profileFields).length, { code: 400, @@ -170,11 +147,7 @@ const updateDiscussion = function(ctx, discussionId, profileFields, callback) { validator .check(field, { code: 400, - msg: - "The field '" + - field + - "' is not a valid field. Must be one of: " + - DISCUSSION_UPDATE_FIELDS.join(', ') + msg: "The field '" + field + "' is not a valid field. Must be one of: " + DISCUSSION_UPDATE_FIELDS.join(', ') }) .isIn(DISCUSSION_UPDATE_FIELDS); if (field === 'visibility') { @@ -186,14 +159,10 @@ const updateDiscussion = function(ctx, discussionId, profileFields, callback) { .isIn(allVisibilities); } else if (field === 'displayName') { validator.check(value, { code: 400, msg: 'A display name cannot be empty' }).notEmpty(); - validator - .check(value, { code: 400, msg: 'A display name can be at most 1000 characters long' }) - .isShortString(); + validator.check(value, { code: 400, msg: 'A display name can be at most 1000 characters long' }).isShortString(); } else if (field === 'description') { validator.check(value, { code: 400, msg: 'A description cannot be empty' }).notEmpty(); - validator - .check(value, { code: 400, msg: 'A description can only be 10000 characters long' }) - .isMediumString(); + validator.check(value, { code: 400, msg: 'A description can only be 10000 characters long' }).isMediumString(); } }); @@ -249,12 +218,8 @@ const updateDiscussion = function(ctx, discussionId, profileFields, callback) { */ const deleteDiscussion = function(ctx, discussionId, callback) { const validator = new Validator(); - validator - .check(discussionId, { code: 400, msg: 'A discussion id must be provided' }) - .isResourceId(); - validator - .check(null, { code: 401, msg: 'You must be authenticated to delete a discussion' }) - .isLoggedInUser(ctx); + validator.check(discussionId, { code: 400, msg: 'A discussion id must be provided' }).isResourceId(); + validator.check(null, { code: 401, msg: 'You must be authenticated to delete a discussion' }).isLoggedInUser(ctx); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -332,9 +297,7 @@ const getDiscussionsLibrary = function(ctx, principalId, start, limit, callback) limit = OaeUtil.getNumberParam(limit, 10, 1); const validator = new Validator(); - validator - .check(principalId, { code: 400, msg: 'A user or group id must be provided' }) - .isPrincipalId(); + validator.check(principalId, { code: 400, msg: 'A user or group id must be provided' }).isPrincipalId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -346,53 +309,49 @@ const getDiscussionsLibrary = function(ctx, principalId, start, limit, callback) } // Determine which library visibility the current user should receive - LibraryAPI.Authz.resolveTargetLibraryAccess( - ctx, - principal.id, - principal, - (err, hasAccess, visibility) => { - if (err) { - return callback(err); - } - if (!hasAccess) { - return callback({ code: 401, msg: 'You do not have have access to this library' }); - } + LibraryAPI.Authz.resolveTargetLibraryAccess(ctx, principal.id, principal, (err, hasAccess, visibility) => { + if (err) { + return callback(err); + } + + if (!hasAccess) { + return callback({ code: 401, msg: 'You do not have have access to this library' }); + } + + // Get the discussion ids from the library index + LibraryAPI.Index.list( + DiscussionsConstants.library.DISCUSSIONS_LIBRARY_INDEX_NAME, + principalId, + visibility, + { start, limit }, + (err, entries, nextToken) => { + if (err) { + return callback(err); + } - // Get the discussion ids from the library index - LibraryAPI.Index.list( - DiscussionsConstants.library.DISCUSSIONS_LIBRARY_INDEX_NAME, - principalId, - visibility, - { start, limit }, - (err, entries, nextToken) => { + // Get the discussion objects from the discussion ids + const discussionIds = _.pluck(entries, 'resourceId'); + DiscussionsDAO.getDiscussionsById(discussionIds, null, (err, discussions) => { if (err) { return callback(err); } - // Get the discussion objects from the discussion ids - const discussionIds = _.pluck(entries, 'resourceId'); - DiscussionsDAO.getDiscussionsById(discussionIds, null, (err, discussions) => { - if (err) { - return callback(err); - } + // Emit an event indicating that the discussion library has been retrieved + DiscussionsAPI.emit( + DiscussionsConstants.events.GET_DISCUSSION_LIBRARY, + ctx, + principalId, + visibility, + start, + limit, + discussions + ); - // Emit an event indicating that the discussion library has been retrieved - DiscussionsAPI.emit( - DiscussionsConstants.events.GET_DISCUSSION_LIBRARY, - ctx, - principalId, - visibility, - start, - limit, - discussions - ); - - return callback(null, discussions, nextToken); - }); - } - ); - } - ); + return callback(null, discussions, nextToken); + }); + } + ); + }); }); }; @@ -407,9 +366,7 @@ const getDiscussionsLibrary = function(ctx, principalId, start, limit, callback) */ const getDiscussion = function(ctx, discussionId, callback) { const validator = new Validator(); - validator - .check(discussionId, { code: 400, msg: 'discussionId must be a valid resource id' }) - .isResourceId(); + validator.check(discussionId, { code: 400, msg: 'discussionId must be a valid resource id' }).isResourceId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -445,9 +402,7 @@ const getDiscussion = function(ctx, discussionId, callback) { */ const getFullDiscussionProfile = function(ctx, discussionId, callback) { const validator = new Validator(); - validator - .check(discussionId, { code: 400, msg: 'discussionId must be a valid resource id' }) - .isResourceId(); + validator.check(discussionId, { code: 400, msg: 'discussionId must be a valid resource id' }).isResourceId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -463,6 +418,7 @@ const getFullDiscussionProfile = function(ctx, discussionId, callback) { if (err) { return callback(err); } + if (!permissions.canView) { // The user has no effective role, which means they are not allowed to view (this has already taken into // consideration implicit privacy rules, such as whether or not the discussion is public). @@ -518,9 +474,7 @@ const getDiscussionMembers = function(ctx, discussionId, start, limit, callback) limit = OaeUtil.getNumberParam(limit, 10, 1); const validator = new Validator(); - validator - .check(discussionId, { code: 400, msg: 'A discussion id must be provided' }) - .isResourceId(); + validator.check(discussionId, { code: 400, msg: 'A discussion id must be provided' }).isResourceId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -568,9 +522,7 @@ const getDiscussionMembers = function(ctx, discussionId, start, limit, callback) */ const getDiscussionInvitations = function(ctx, discussionId, callback) { const validator = new Validator(); - validator - .check(discussionId, { code: 400, msg: 'A valid resource id must be specified' }) - .isResourceId(); + validator.check(discussionId, { code: 400, msg: 'A valid resource id must be specified' }).isResourceId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -595,9 +547,7 @@ const getDiscussionInvitations = function(ctx, discussionId, callback) { */ const resendDiscussionInvitation = function(ctx, discussionId, email, callback) { const validator = new Validator(); - validator - .check(discussionId, { code: 400, msg: 'A valid resource id must be specified' }) - .isResourceId(); + validator.check(discussionId, { code: 400, msg: 'A valid resource id must be specified' }).isResourceId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -627,9 +577,7 @@ const shareDiscussion = function(ctx, discussionId, principalIds, callback) { validator .check(null, { code: 401, msg: 'You have to be logged in to be able to share a discussion' }) .isLoggedInUser(ctx); - validator - .check(discussionId, { code: 400, msg: 'A valid discussion id must be provided' }) - .isResourceId(); + validator.check(discussionId, { code: 400, msg: 'A valid discussion id must be provided' }).isResourceId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -639,35 +587,30 @@ const shareDiscussion = function(ctx, discussionId, principalIds, callback) { return callback(err); } - ResourceActions.share( - ctx, - discussion, - principalIds, - AuthzConstants.role.MEMBER, - (err, memberChangeInfo) => { - if (err) { - return callback(err); - } - if (_.isEmpty(memberChangeInfo.changes)) { - return callback(); - } + ResourceActions.share(ctx, discussion, principalIds, AuthzConstants.role.MEMBER, (err, memberChangeInfo) => { + if (err) { + return callback(err); + } - DiscussionsAPI.emit( - DiscussionsConstants.events.UPDATED_DISCUSSION_MEMBERS, - ctx, - discussion, - memberChangeInfo, - {}, - errs => { - if (errs) { - return callback(_.first(errs)); - } + if (_.isEmpty(memberChangeInfo.changes)) { + return callback(); + } - return callback(); + DiscussionsAPI.emit( + DiscussionsConstants.events.UPDATED_DISCUSSION_MEMBERS, + ctx, + discussion, + memberChangeInfo, + {}, + errs => { + if (errs) { + return callback(_.first(errs)); } - ); - } - ); + + return callback(); + } + ); + }); }); }; @@ -690,9 +633,7 @@ const setDiscussionPermissions = function(ctx, discussionId, changes, callback) msg: 'You have to be logged in to be able to change discussion permissions' }) .isLoggedInUser(ctx); - validator - .check(discussionId, { code: 400, msg: 'A valid discussion id must be provided' }) - .isResourceId(); + validator.check(discussionId, { code: 400, msg: 'A valid discussion id must be provided' }).isResourceId(); _.each(changes, (role, principalId) => { validator .check(role, { @@ -729,6 +670,7 @@ const setDiscussionPermissions = function(ctx, discussionId, changes, callback) if (err) { return callback(err); } + if (_.isEmpty(memberChangeInfo.changes)) { return callback(); } @@ -771,9 +713,7 @@ const removeDiscussionFromLibrary = function(ctx, libraryOwnerId, discussionId, msg: 'You must be authenticated to remove a discussion from a library' }) .isLoggedInUser(ctx); - validator - .check(libraryOwnerId, { code: 400, msg: 'A user or group id must be provided' }) - .isPrincipalId(); + validator.check(libraryOwnerId, { code: 400, msg: 'A user or group id must be provided' }).isPrincipalId(); validator .check(discussionId, { code: 400, @@ -840,20 +780,12 @@ const removeDiscussionFromLibrary = function(ctx, libraryOwnerId, discussionId, */ const createMessage = function(ctx, discussionId, body, replyToCreatedTimestamp, callback) { const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'Only authenticated users can post on discussions' }) - .isLoggedInUser(ctx); - validator - .check(discussionId, { code: 400, msg: 'Invalid discussion id provided' }) - .isResourceId(); + validator.check(null, { code: 401, msg: 'Only authenticated users can post on discussions' }).isLoggedInUser(ctx); + validator.check(discussionId, { code: 400, msg: 'Invalid discussion id provided' }).isResourceId(); validator.check(body, { code: 400, msg: 'A discussion body must be provided' }).notEmpty(); - validator - .check(body, { code: 400, msg: 'A discussion body can only be 100000 characters long' }) - .isLongString(); + validator.check(body, { code: 400, msg: 'A discussion body can only be 100000 characters long' }).isLongString(); if (replyToCreatedTimestamp) { - validator - .check(replyToCreatedTimestamp, { code: 400, msg: 'Invalid reply-to timestamp provided' }) - .isInt(); + validator.check(replyToCreatedTimestamp, { code: 400, msg: 'Invalid reply-to timestamp provided' }).isInt(); } if (validator.hasErrors()) { @@ -925,12 +857,8 @@ const createMessage = function(ctx, discussionId, body, replyToCreatedTimestamp, */ const deleteMessage = function(ctx, discussionId, messageCreatedDate, callback) { const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'Only authenticated users can delete messages' }) - .isLoggedInUser(ctx); - validator - .check(discussionId, { code: 400, msg: 'A discussion id must be provided' }) - .isResourceId(); + validator.check(null, { code: 401, msg: 'Only authenticated users can delete messages' }).isLoggedInUser(ctx); + validator.check(discussionId, { code: 400, msg: 'A discussion id must be provided' }).isResourceId(); validator .check(messageCreatedDate, { code: 400, @@ -948,54 +876,51 @@ const deleteMessage = function(ctx, discussionId, messageCreatedDate, callback) } // Ensure that the message exists. We also need it so we can make sure we have access to deleted it - MessageBoxAPI.getMessages( - discussionId, - [messageCreatedDate], - { scrubDeleted: false }, - (err, messages) => { + MessageBoxAPI.getMessages(discussionId, [messageCreatedDate], { scrubDeleted: false }, (err, messages) => { + if (err) { + return callback(err); + } + + if (!messages[0]) { + return callback({ code: 404, msg: 'The specified message does not exist' }); + } + + const message = messages[0]; + + // Determine if we have access to delete the discussion message + AuthzPermissions.canManageMessage(ctx, discussion, message, err => { if (err) { return callback(err); } - if (!messages[0]) { - return callback({ code: 404, msg: 'The specified message does not exist' }); - } - const message = messages[0]; - - // Determine if we have access to delete the discussion message - AuthzPermissions.canManageMessage(ctx, discussion, message, err => { - if (err) { - return callback(err); - } + // Delete the message using the "leaf" method, which will SOFT delete if the message has replies, or HARD delete if it does not + MessageBoxAPI.deleteMessage( + discussionId, + messageCreatedDate, + { deleteType: MessageBoxConstants.deleteTypes.LEAF }, + (err, deleteType, deletedMessage) => { + if (err) { + return callback(err); + } - // Delete the message using the "leaf" method, which will SOFT delete if the message has replies, or HARD delete if it does not - MessageBoxAPI.deleteMessage( - discussionId, - messageCreatedDate, - { deleteType: MessageBoxConstants.deleteTypes.LEAF }, - (err, deleteType, deletedMessage) => { - if (err) { - return callback(err); - } + DiscussionsAPI.emit( + DiscussionsConstants.events.DELETED_DISCUSSION_MESSAGE, + ctx, + message, + discussion, + deleteType + ); - DiscussionsAPI.emit( - DiscussionsConstants.events.DELETED_DISCUSSION_MESSAGE, - ctx, - message, - discussion, - deleteType - ); - - // If a soft-delete occurred, we want to inform the consumer of the soft-delete message model - if (deleteType === MessageBoxConstants.deleteTypes.SOFT) { - return callback(null, deletedMessage); - } - return callback(); + // If a soft-delete occurred, we want to inform the consumer of the soft-delete message model + if (deleteType === MessageBoxConstants.deleteTypes.SOFT) { + return callback(null, deletedMessage); } - ); - }); - } - ); + + return callback(); + } + ); + }); + }); }); }; @@ -1015,9 +940,7 @@ const getMessages = function(ctx, discussionId, start, limit, callback) { limit = OaeUtil.getNumberParam(limit, 10, 1); const validator = new Validator(); - validator - .check(discussionId, { code: 400, msg: 'Must provide a valid discussion id' }) - .isResourceId(); + validator.check(discussionId, { code: 400, msg: 'Must provide a valid discussion id' }).isResourceId(); validator.check(limit, { code: 400, msg: 'Must provide a valid limit' }).isInt(); if (validator.hasErrors()) { return callback(validator.getFirstError()); @@ -1030,40 +953,34 @@ const getMessages = function(ctx, discussionId, start, limit, callback) { } // Fetch the messages from the message box - MessageBoxAPI.getMessagesFromMessageBox( - discussionId, - start, - limit, - null, - (err, messages, nextToken) => { + MessageBoxAPI.getMessagesFromMessageBox(discussionId, start, limit, null, (err, messages, nextToken) => { + if (err) { + return callback(err); + } + + let userIds = _.map(messages, message => { + return message.createdBy; + }); + + // Remove falsey and duplicate userIds + userIds = _.uniq(_.compact(userIds)); + + // Get the basic principal profiles of the messagers to add to the messages as `createdBy`. + PrincipalsUtil.getPrincipals(ctx, userIds, (err, users) => { if (err) { return callback(err); } - let userIds = _.map(messages, message => { - return message.createdBy; - }); - - // Remove falsey and duplicate userIds - userIds = _.uniq(_.compact(userIds)); - - // Get the basic principal profiles of the messagers to add to the messages as `createdBy`. - PrincipalsUtil.getPrincipals(ctx, userIds, (err, users) => { - if (err) { - return callback(err); + // Attach the user profiles to the message objects + _.each(messages, message => { + if (users[message.createdBy]) { + message.createdBy = users[message.createdBy]; } - - // Attach the user profiles to the message objects - _.each(messages, message => { - if (users[message.createdBy]) { - message.createdBy = users[message.createdBy]; - } - }); - - return callback(err, messages, nextToken); }); - } - ); + + return callback(err, messages, nextToken); + }); + }); }); }; @@ -1083,6 +1000,7 @@ const _getDiscussion = function(discussionId, callback) { if (err) { return callback(err); } + if (!discussion) { return callback({ code: 404, msg: 'Could not find discussion: ' + discussionId }); } @@ -1091,7 +1009,7 @@ const _getDiscussion = function(discussionId, callback) { }); }; -module.exports = { +export { createDiscussion, updateDiscussion, deleteDiscussion, diff --git a/packages/oae-discussions/lib/api.js b/packages/oae-discussions/lib/api.js index ff02f4d210..f7e15753d4 100644 --- a/packages/oae-discussions/lib/api.js +++ b/packages/oae-discussions/lib/api.js @@ -13,7 +13,9 @@ * permissions and limitations under the License. */ -const EmitterAPI = require('oae-emitter'); +import * as EmitterAPI from 'oae-emitter'; + +import * as Discussions from './api.discussions'; /** * ### Events @@ -30,6 +32,6 @@ const EmitterAPI = require('oae-emitter'); * * `updatedDiscussionMembers(ctx, discussion, memberUpdates, newMemberIds, updatedMemberIds, removedMemberIds)`: The members and/or managers for a discussion have been altered. */ const DiscussionsAPI = new EmitterAPI.EventEmitter(); -module.exports = DiscussionsAPI; +export default DiscussionsAPI; -module.exports.Discussions = require('./api.discussions'); +export { Discussions }; diff --git a/packages/oae-discussions/lib/constants.js b/packages/oae-discussions/lib/constants.js index f96980f8e3..89a50ac034 100644 --- a/packages/oae-discussions/lib/constants.js +++ b/packages/oae-discussions/lib/constants.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const { AuthzConstants } = require('oae-authz/lib/constants'); +import { AuthzConstants } from 'oae-authz/lib/constants'; const DiscussionsConstants = {}; @@ -56,4 +56,4 @@ DiscussionsConstants.search = { MAPPING_DISCUSSION_MESSAGE: 'discussion_message' }; -module.exports = { DiscussionsConstants }; +export { DiscussionsConstants }; diff --git a/packages/oae-discussions/lib/init.js b/packages/oae-discussions/lib/init.js index 51dbf13b3a..7880a1b6d4 100644 --- a/packages/oae-discussions/lib/init.js +++ b/packages/oae-discussions/lib/init.js @@ -14,18 +14,17 @@ */ /* eslint-disable no-unused-vars */ -const DiscussionsSearch = require('./search'); +import * as DiscussionsSearch from './search'; -module.exports = function(config, callback) { +export function init(config, callback) { // Register the library functionality const library = require('./library'); // Register the activity functionality - const activity = require('./activity'); // Register the invitations functionality const invitations = require('./invitations'); return DiscussionsSearch.init(callback); -}; +} diff --git a/packages/oae-discussions/lib/internal/dao.js b/packages/oae-discussions/lib/internal/dao.js index b6b7cb25d4..b836e7923b 100644 --- a/packages/oae-discussions/lib/internal/dao.js +++ b/packages/oae-discussions/lib/internal/dao.js @@ -13,17 +13,19 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); -const ShortId = require('shortid'); +import util from 'util'; +import _ from 'underscore'; +import ShortId from 'shortid'; -const AuthzUtil = require('oae-authz/lib/util'); -const Cassandra = require('oae-util/lib/cassandra'); -const log = require('oae-logger').logger('discussions-dao'); -const OaeUtil = require('oae-util/lib/util'); -const TenantsAPI = require('oae-tenants'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import { logger } from 'oae-logger'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as TenantsAPI from 'oae-tenants'; -const { Discussion } = require('oae-discussions/lib/model'); +import { Discussion } from 'oae-discussions/lib/model'; + +const log = logger('discussions-dao'); /** * Create a new discussion. @@ -196,10 +198,10 @@ const iterateAll = function(properties, batchSize, onEach, callback) { } /*! - * Handles each batch from the cassandra iterateAll method - * - * @see Cassandra#iterateAll - */ + * Handles each batch from the cassandra iterateAll method + * + * @see Cassandra#iterateAll + */ const _iterateAllOnEach = function(rows, done) { // Convert the rows to a hash and delegate action to the caller onEach method return onEach(_.map(rows, Cassandra.rowToHash), done); @@ -261,11 +263,4 @@ const _createDiscussionId = function(tenantAlias) { return AuthzUtil.toId('d', tenantAlias, ShortId.generate()); }; -module.exports = { - createDiscussion, - updateDiscussion, - getDiscussion, - deleteDiscussion, - getDiscussionsById, - iterateAll -}; +export { createDiscussion, updateDiscussion, getDiscussion, deleteDiscussion, getDiscussionsById, iterateAll }; diff --git a/packages/oae-discussions/lib/invitations.js b/packages/oae-discussions/lib/invitations.js index 6c1c0d3b15..72804bfdde 100644 --- a/packages/oae-discussions/lib/invitations.js +++ b/packages/oae-discussions/lib/invitations.js @@ -13,20 +13,24 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import DiscussionsAPI from 'oae-discussions'; -const AuthzInvitationsDAO = require('oae-authz/lib/invitations/dao'); -const AuthzUtil = require('oae-authz/lib/util'); -const { Context } = require('oae-context'); -const { Invitation } = require('oae-authz/lib/invitations/model'); -const ResourceActions = require('oae-resource/lib/actions'); -const { ResourceConstants } = require('oae-resource/lib/constants'); +import _ from 'underscore'; -const DiscussionsAPI = require('oae-discussions'); -const { DiscussionsConstants } = require('oae-discussions/lib/constants'); -const DiscussionsDAO = require('oae-discussions/lib/internal/dao'); +import * as AuthzInvitationsDAO from 'oae-authz/lib/invitations/dao'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ResourceActions from 'oae-resource/lib/actions'; +import * as DiscussionsDAO from 'oae-discussions/lib/internal/dao'; -const log = require('oae-logger').logger('oae-discussions-invitations'); +import { Context } from 'oae-context'; +import { Invitation } from 'oae-authz/lib/invitations/model'; +import { ResourceConstants } from 'oae-resource/lib/constants'; + +import { DiscussionsConstants } from 'oae-discussions/lib/constants'; + +import { logger } from 'oae-logger'; + +const log = logger('oae-discussions-invitations'); /*! * When an invitation is accepted, pass on the events to update discussion members and then feed @@ -84,24 +88,21 @@ ResourceActions.emitter.when( /*! * When a discussion is deleted, we delete all invitations associated to it */ -DiscussionsAPI.when( - DiscussionsConstants.events.DELETED_DISCUSSION, - (ctx, discussion, memberIds, callback) => { - AuthzInvitationsDAO.deleteInvitationsByResourceId(discussion.id, err => { - if (err) { - log().warn( - { - err, - discussionId: discussion.id - }, - 'An error occurred while removing invitations after a discussion was deleted' - ); - } +DiscussionsAPI.when(DiscussionsConstants.events.DELETED_DISCUSSION, (ctx, discussion, memberIds, callback) => { + AuthzInvitationsDAO.deleteInvitationsByResourceId(discussion.id, err => { + if (err) { + log().warn( + { + err, + discussionId: discussion.id + }, + 'An error occurred while removing invitations after a discussion was deleted' + ); + } - return callback(); - }); - } -); + return callback(); + }); +}); /** * Determine if the given id is a discussion id diff --git a/packages/oae-discussions/lib/library.js b/packages/oae-discussions/lib/library.js index 1c574a0a7e..dc3630ec77 100644 --- a/packages/oae-discussions/lib/library.js +++ b/packages/oae-discussions/lib/library.js @@ -13,15 +13,18 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const AuthzAPI = require('oae-authz'); -const LibraryAPI = require('oae-library'); -const OaeUtil = require('oae-util/lib/util'); -const log = require('oae-logger'); +import DiscussionsAPI from 'oae-discussions'; -const DiscussionsAPI = require('oae-discussions'); -const { DiscussionsConstants } = require('oae-discussions/lib/constants'); -const DiscussionsDAO = require('oae-discussions/lib/internal/dao'); +import _ from 'underscore'; +import * as AuthzAPI from 'oae-authz'; +import * as LibraryAPI from 'oae-library'; +import * as OaeUtil from 'oae-util/lib/util'; +import { logger } from 'oae-logger'; + +import { DiscussionsConstants } from 'oae-discussions/lib/constants'; +import * as DiscussionsDAO from 'oae-discussions/lib/internal/dao'; + +const log = logger('oae-discussions'); // When updating discussions as a result of new messages, update it at most every hour const LIBRARY_UPDATE_THRESHOLD_SECONDS = 3600; @@ -32,40 +35,34 @@ const LIBRARY_UPDATE_THRESHOLD_SECONDS = 3600; LibraryAPI.Index.registerLibraryIndex(DiscussionsConstants.library.DISCUSSIONS_LIBRARY_INDEX_NAME, { pageResources(libraryId, start, limit, callback) { // Query all the discussion ids ('d') to which the library owner is directly associated in this batch of paged resources - AuthzAPI.getRolesForPrincipalAndResourceType( - libraryId, - 'd', - start, - limit, - (err, roles, nextToken) => { - if (err) { - return callback(err); - } + AuthzAPI.getRolesForPrincipalAndResourceType(libraryId, 'd', start, limit, (err, roles, nextToken) => { + if (err) { + return callback(err); + } - // We just need the ids, not the roles - const ids = _.pluck(roles, 'id'); + // We just need the ids, not the roles + const ids = _.pluck(roles, 'id'); - DiscussionsDAO.getDiscussionsById( - ids, - ['id', 'tenantAlias', 'visibility', 'lastModified'], - (err, discussions) => { - if (err) { - return callback(err); - } + DiscussionsDAO.getDiscussionsById( + ids, + ['id', 'tenantAlias', 'visibility', 'lastModified'], + (err, discussions) => { + if (err) { + return callback(err); + } - // Convert all the discussions into the light-weight library items that describe how its placed in a library index - const resources = _.chain(discussions) - .compact() - .map(discussion => { - return { rank: discussion.lastModified, resource: discussion }; - }) - .value(); + // Convert all the discussions into the light-weight library items that describe how its placed in a library index + const resources = _.chain(discussions) + .compact() + .map(discussion => { + return { rank: discussion.lastModified, resource: discussion }; + }) + .value(); - return callback(null, resources, nextToken); - } - ); - } - ); + return callback(null, resources, nextToken); + } + ); + }); } }); @@ -77,75 +74,66 @@ LibraryAPI.Search.registerLibrarySearch('discussion-library', ['discussion']); /*! * When a discussion is created, add the discussion to the member discussion libraries */ -DiscussionsAPI.when( - DiscussionsConstants.events.CREATED_DISCUSSION, - (ctx, discussion, memberChangeInfo, callback) => { - const addedMemberIds = _.pluck(memberChangeInfo.members.added, 'id'); - _insertLibrary(addedMemberIds, discussion, err => { - if (err) { - log().warn( - { - err, - discussionId: discussion.id, - memberIds: addedMemberIds - }, - 'An error occurred inserting discussion into discussion libraries after create' - ); - } +DiscussionsAPI.when(DiscussionsConstants.events.CREATED_DISCUSSION, (ctx, discussion, memberChangeInfo, callback) => { + const addedMemberIds = _.pluck(memberChangeInfo.members.added, 'id'); + _insertLibrary(addedMemberIds, discussion, err => { + if (err) { + log().warn( + { + err, + discussionId: discussion.id, + memberIds: addedMemberIds + }, + 'An error occurred inserting discussion into discussion libraries after create' + ); + } - return callback(); - }); - } -); + return callback(); + }); +}); /*! * When a discussion is updated, update all discussion libraries with its updated last modified * date */ -DiscussionsAPI.on( - DiscussionsConstants.events.UPDATED_DISCUSSION, - (ctx, updatedDiscussion, oldDiscussion) => { - // Get all the member ids, we will update their discussion libraries - _getAllMemberIds(updatedDiscussion.id, (err, memberIds) => { - if (err) { - log().warn( - { - err, - discussionId: updatedDiscussion.id, - memberIds - }, - 'An error occurred while updating a discussion in all discussion libraries' - ); - } +DiscussionsAPI.on(DiscussionsConstants.events.UPDATED_DISCUSSION, (ctx, updatedDiscussion, oldDiscussion) => { + // Get all the member ids, we will update their discussion libraries + _getAllMemberIds(updatedDiscussion.id, (err, memberIds) => { + if (err) { + log().warn( + { + err, + discussionId: updatedDiscussion.id, + memberIds + }, + 'An error occurred while updating a discussion in all discussion libraries' + ); + } - // Perform all the library updates - return _updateLibrary(memberIds, updatedDiscussion, oldDiscussion.lastModified); - }); - } -); + // Perform all the library updates + return _updateLibrary(memberIds, updatedDiscussion, oldDiscussion.lastModified); + }); +}); /** * When a discussion is deleted, remove it from all discussion libraries */ -DiscussionsAPI.when( - DiscussionsConstants.events.DELETED_DISCUSSION, - (ctx, discussion, removedMemberIds, callback) => { - // Remove the discussion from all libraries - _removeLibrary(removedMemberIds, discussion, err => { - if (err) { - log().warn( - { - err, - discussionId: discussion.id - }, - 'An error occurred while removing a deleted discussion from all discussion libraries' - ); - } +DiscussionsAPI.when(DiscussionsConstants.events.DELETED_DISCUSSION, (ctx, discussion, removedMemberIds, callback) => { + // Remove the discussion from all libraries + _removeLibrary(removedMemberIds, discussion, err => { + if (err) { + log().warn( + { + err, + discussionId: discussion.id + }, + 'An error occurred while removing a deleted discussion from all discussion libraries' + ); + } - return callback(); - }); - } -); + return callback(); + }); +}); /** * When a discussions members are updated, pass the required updates to its members library as well @@ -245,48 +233,45 @@ DiscussionsAPI.when( * When a new message is created for the discussion, update its last modified date and update its * rank in all discussion libraries */ -DiscussionsAPI.on( - DiscussionsConstants.events.CREATED_DISCUSSION_MESSAGE, - (ctx, message, discussion) => { - // Check to see if we are in a threshold to perform a discussion lastModified update. If not, we - // don't promote the discussion the library ranks - if (!_testDiscussionUpdateThreshold(discussion)) { - return; +DiscussionsAPI.on(DiscussionsConstants.events.CREATED_DISCUSSION_MESSAGE, (ctx, message, discussion) => { + // Check to see if we are in a threshold to perform a discussion lastModified update. If not, we + // don't promote the discussion the library ranks + if (!_testDiscussionUpdateThreshold(discussion)) { + return; + } + + // Try and get the principals whose libraries will be updated + _getAllMemberIds(discussion.id, (err, memberIds) => { + if (err) { + // If we can't get the members, don't so that we don't risk + return log().warn( + { + err, + discussionId: discussion.id, + memberIds + }, + 'Error fetching discussion members list to update library. Skipping updating libraries' + ); } - // Try and get the principals whose libraries will be updated - _getAllMemberIds(discussion.id, (err, memberIds) => { + // Update the lastModified of the discussion + _touch(discussion, (err, updatedDiscussion) => { if (err) { - // If we can't get the members, don't so that we don't risk + // If we get an error touching the discussion, we simply won't update the libraries. Better luck next time. return log().warn( { err, discussionId: discussion.id, memberIds }, - 'Error fetching discussion members list to update library. Skipping updating libraries' + 'Error touching discussion to update lastModified time. Skipping updating libraries' ); } - // Update the lastModified of the discussion - _touch(discussion, (err, updatedDiscussion) => { - if (err) { - // If we get an error touching the discussion, we simply won't update the libraries. Better luck next time. - return log().warn( - { - err, - discussionId: discussion.id, - memberIds - }, - 'Error touching discussion to update lastModified time. Skipping updating libraries' - ); - } - - return _updateLibrary(memberIds, updatedDiscussion, discussion.lastModified); - }); + return _updateLibrary(memberIds, updatedDiscussion, discussion.lastModified); }); - } -); + }); +}); /** * Perform a "touch" on a discussion, which updates only the lastModified date of the discussion @@ -309,10 +294,7 @@ const _touch = function(discussion, callback) { * @api private */ const _testDiscussionUpdateThreshold = function(discussion) { - return ( - !discussion.lastModified || - Date.now() - discussion.lastModified > LIBRARY_UPDATE_THRESHOLD_SECONDS * 1000 - ); + return !discussion.lastModified || Date.now() - discussion.lastModified > LIBRARY_UPDATE_THRESHOLD_SECONDS * 1000; }; /** @@ -372,11 +354,7 @@ const _insertLibrary = function(principalIds, discussion, callback) { }; }); - LibraryAPI.Index.insert( - DiscussionsConstants.library.DISCUSSIONS_LIBRARY_INDEX_NAME, - entries, - callback - ); + LibraryAPI.Index.insert(DiscussionsConstants.library.DISCUSSIONS_LIBRARY_INDEX_NAME, entries, callback); }; /** @@ -419,11 +397,7 @@ const _updateLibrary = function(principalIds, discussion, oldLastModified, callb }; }); - LibraryAPI.Index.update( - DiscussionsConstants.library.DISCUSSIONS_LIBRARY_INDEX_NAME, - entries, - callback - ); + LibraryAPI.Index.update(DiscussionsConstants.library.DISCUSSIONS_LIBRARY_INDEX_NAME, entries, callback); }; /** @@ -463,9 +437,5 @@ const _removeLibrary = function(principalIds, discussion, callback) { }; }); - LibraryAPI.Index.remove( - DiscussionsConstants.library.DISCUSSIONS_LIBRARY_INDEX_NAME, - entries, - callback - ); + LibraryAPI.Index.remove(DiscussionsConstants.library.DISCUSSIONS_LIBRARY_INDEX_NAME, entries, callback); }; diff --git a/packages/oae-discussions/lib/migration.js b/packages/oae-discussions/lib/migration.js index f5a49db718..a2fe5adfc8 100644 --- a/packages/oae-discussions/lib/migration.js +++ b/packages/oae-discussions/lib/migration.js @@ -1,4 +1,4 @@ -const Cassandra = require('oae-util/lib/cassandra'); +import Cassandra from 'oae-util/lib/cassandra'; /** * Ensure that the all of the discussion schemas are created. If they already exist, this method will not do anything @@ -16,4 +16,5 @@ const ensureSchema = function(callback) { callback ); }; -module.exports = { ensureSchema }; + +export { ensureSchema }; diff --git a/packages/oae-discussions/lib/model.js b/packages/oae-discussions/lib/model.js index 0958e64895..df558f2e70 100644 --- a/packages/oae-discussions/lib/model.js +++ b/packages/oae-discussions/lib/model.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const util = require('util'); +import util from 'util'; -const AuthzUtil = require('oae-authz/lib/util'); +import * as AuthzUtil from 'oae-authz/lib/util'; /** * A model object that represents a discussion object. @@ -30,16 +30,7 @@ const AuthzUtil = require('oae-authz/lib/util'); * @param {Number} lastModified The timestamp (millis since epoch) at which the discussion was last modified (or received the last message) * @return {Discussion} The discussion with the data provided */ -const Discussion = function( - tenant, - id, - createdBy, - displayName, - description, - visibility, - created, - lastModified -) { +const Discussion = function(tenant, id, createdBy, displayName, description, visibility, created, lastModified) { const { resourceId } = AuthzUtil.getResourceFromId(id); const that = {}; that.tenant = tenant; @@ -55,4 +46,4 @@ const Discussion = function( return that; }; -module.exports = { Discussion }; +export { Discussion }; diff --git a/packages/oae-discussions/lib/rest.js b/packages/oae-discussions/lib/rest.js index f14b153052..74b08bc9bf 100644 --- a/packages/oae-discussions/lib/rest.js +++ b/packages/oae-discussions/lib/rest.js @@ -13,13 +13,13 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import * as DiscussionsAPI from 'oae-discussions'; -const { AuthzConstants } = require('oae-authz/lib/constants'); -const OAE = require('oae-util/lib/oae'); -const OaeUtil = require('oae-util/lib/util'); +import _ from 'underscore'; -const DiscussionsAPI = require('oae-discussions'); +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as OAE from 'oae-util/lib/oae'; +import * as OaeUtil from 'oae-util/lib/util'; /** * @REST postDiscussionCreate @@ -102,18 +102,13 @@ OAE.tenantRouter.on('post', '/api/discussion/create', (req, res) => { * @HttpResponse 404 Could not find the specified discussion */ OAE.tenantRouter.on('post', '/api/discussion/:discussionId', (req, res) => { - DiscussionsAPI.Discussions.updateDiscussion( - req.ctx, - req.params.discussionId, - req.body, - (err, discussion) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - res.status(200).send(discussion); + DiscussionsAPI.Discussions.updateDiscussion(req.ctx, req.params.discussionId, req.body, (err, discussion) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(200).send(discussion); + }); }); /** @@ -222,17 +217,13 @@ OAE.tenantRouter.on('delete', '/api/discussion/library/:principalId/:discussionI * @HttpResponse 404 Could not find the specified discussion */ OAE.tenantRouter.on('get', '/api/discussion/:discussionId', (req, res) => { - DiscussionsAPI.Discussions.getFullDiscussionProfile( - req.ctx, - req.params.discussionId, - (err, discussion) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - res.status(200).send(discussion); + DiscussionsAPI.Discussions.getFullDiscussionProfile(req.ctx, req.params.discussionId, (err, discussion) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(200).send(discussion); + }); }); /** @@ -264,6 +255,7 @@ OAE.tenantRouter.on('post', '/api/discussion/:discussionId/share', (req, res) => if (err) { return res.status(err.code).send(err.msg); } + res.status(200).end(); }); }); @@ -297,17 +289,13 @@ OAE.tenantRouter.on('post', '/api/discussion/:discussionId/members', (req, res) permissionUpdates[key] = OaeUtil.castToBoolean(value); }); - DiscussionsAPI.Discussions.setDiscussionPermissions( - req.ctx, - req.params.discussionId, - permissionUpdates, - err => { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(200).end(); + DiscussionsAPI.Discussions.setDiscussionPermissions(req.ctx, req.params.discussionId, permissionUpdates, err => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(200).end(); + }); }); /** @@ -360,17 +348,13 @@ OAE.tenantRouter.on('get', '/api/discussion/:discussionId/members', (req, res) = * @HttpResponse 404 Discussion not available */ OAE.tenantRouter.on('get', '/api/discussion/:discussionId/invitations', (req, res) => { - DiscussionsAPI.Discussions.getDiscussionInvitations( - req.ctx, - req.params.discussionId, - (err, invitations) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(200).send({ results: invitations }); + DiscussionsAPI.Discussions.getDiscussionInvitations(req.ctx, req.params.discussionId, (err, invitations) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + return res.status(200).send({ results: invitations }); + }); }); /** @@ -391,24 +375,15 @@ OAE.tenantRouter.on('get', '/api/discussion/:discussionId/invitations', (req, re * @HttpResponse 404 Discussion not available * @HttpResponse 404 No invitation for the specified email exists for the discussion */ -OAE.tenantRouter.on( - 'post', - '/api/discussion/:discussionId/invitations/:email/resend', - (req, res) => { - DiscussionsAPI.Discussions.resendDiscussionInvitation( - req.ctx, - req.params.discussionId, - req.params.email, - err => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(200).end(); - } - ); - } -); +OAE.tenantRouter.on('post', '/api/discussion/:discussionId/invitations/:email/resend', (req, res) => { + DiscussionsAPI.Discussions.resendDiscussionInvitation(req.ctx, req.params.discussionId, req.params.email, err => { + if (err) { + return res.status(err.code).send(err.msg); + } + + return res.status(200).end(); + }); +}); /** * @REST getDiscussionDiscussionIdMessages @@ -507,16 +482,11 @@ OAE.tenantRouter.on('post', '/api/discussion/:discussionId/messages', (req, res) * @HttpResponse 404 Could not find the specified message */ OAE.tenantRouter.on('delete', '/api/discussion/:discussionId/messages/:created', (req, res) => { - DiscussionsAPI.Discussions.deleteMessage( - req.ctx, - req.params.discussionId, - req.params.created, - (err, message) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - res.status(200).send(message); + DiscussionsAPI.Discussions.deleteMessage(req.ctx, req.params.discussionId, req.params.created, (err, message) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(200).send(message); + }); }); diff --git a/packages/oae-discussions/lib/search.js b/packages/oae-discussions/lib/search.js index d0b8583b8d..1886db2139 100644 --- a/packages/oae-discussions/lib/search.js +++ b/packages/oae-discussions/lib/search.js @@ -13,18 +13,19 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const log = require('oae-logger').logger('discussions-search'); -const MessageBoxSearch = require('oae-messagebox/lib/search'); -const SearchAPI = require('oae-search'); -const TenantsAPI = require('oae-tenants'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as MessageBoxSearch from 'oae-messagebox/lib/search'; +import * as SearchAPI from 'oae-search'; +import * as TenantsAPI from 'oae-tenants'; +import { logger } from 'oae-logger'; +import * as DiscussionsDAO from './internal/dao'; +import DiscussionsAPI from './api'; +import { DiscussionsConstants } from './constants'; -const DiscussionsAPI = require('./api'); -const { DiscussionsConstants } = require('./constants'); -const DiscussionsDAO = require('./internal/dao'); +const log = logger('discussions-search'); /** * Initializes the child search documents for the Discussions module @@ -91,36 +92,30 @@ DiscussionsAPI.on(DiscussionsConstants.events.DELETED_DISCUSSION, (ctx, discussi /*! * When a message is added to a discussion, we must index the child message document */ -DiscussionsAPI.on( - DiscussionsConstants.events.CREATED_DISCUSSION_MESSAGE, - (ctx, message, discussion) => { - const resource = { - id: discussion.id, - messages: [message] - }; - - SearchAPI.postIndexTask('discussion', [resource], { - children: { - // eslint-disable-next-line camelcase - discussion_message: true - } - }); - } -); +DiscussionsAPI.on(DiscussionsConstants.events.CREATED_DISCUSSION_MESSAGE, (ctx, message, discussion) => { + const resource = { + id: discussion.id, + messages: [message] + }; + + SearchAPI.postIndexTask('discussion', [resource], { + children: { + // eslint-disable-next-line camelcase + discussion_message: true + } + }); +}); /*! * When a discussion message is deleted, we must delete the child message document */ -DiscussionsAPI.on( - DiscussionsConstants.events.DELETED_DISCUSSION_MESSAGE, - (ctx, message, discussion, _) => { - return MessageBoxSearch.deleteMessageSearchDocument( - DiscussionsConstants.search.MAPPING_DISCUSSION_MESSAGE, - discussion.id, - message - ); - } -); +DiscussionsAPI.on(DiscussionsConstants.events.DELETED_DISCUSSION_MESSAGE, (ctx, message, discussion, _) => { + return MessageBoxSearch.deleteMessageSearchDocument( + DiscussionsConstants.search.MAPPING_DISCUSSION_MESSAGE, + discussion.id, + message + ); +}); /// ///////////////////// // DOCUMENT PRODUCERS // @@ -310,12 +305,12 @@ SearchAPI.registerSearchDocumentTransformer('discussion', _transformDiscussionDo SearchAPI.registerReindexAllHandler('discussion', callback => { /*! - * Handles each iteration of the DiscussionDAO iterate all method, firing tasks for all discussions to - * be reindexed. - * - * @see DiscussionDAO#iterateAll - * @api private - */ + * Handles each iteration of the DiscussionDAO iterate all method, firing tasks for all discussions to + * be reindexed. + * + * @see DiscussionDAO#iterateAll + * @api private + */ const _onEach = function(discussionRows, done) { // Batch up this iteration of task resources const discussionResources = []; @@ -333,6 +328,4 @@ SearchAPI.registerReindexAllHandler('discussion', callback => { DiscussionsDAO.iterateAll(['id'], 100, _onEach, callback); }); -module.exports = { - init -}; +export { init }; diff --git a/packages/oae-discussions/lib/test/util.js b/packages/oae-discussions/lib/test/util.js index 4655ce11b1..a10054e092 100644 --- a/packages/oae-discussions/lib/test/util.js +++ b/packages/oae-discussions/lib/test/util.js @@ -14,15 +14,15 @@ */ /* eslint-disable no-unused-vars */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const AuthzTestUtil = require('oae-authz/lib/test/util'); -const LibraryAPI = require('oae-library'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); -const RestAPI = require('oae-rest'); -const SearchTestUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests/lib/util'); +import * as AuthzTestUtil from 'oae-authz/lib/test/util'; +import * as LibraryAPI from 'oae-library'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as SearchTestUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests/lib/util'; /** * Set up 2 public tenants and 2 private tenants, each with a public, loggedin, private set of users and @@ -55,20 +55,18 @@ const TestsUtil = require('oae-tests/lib/util'); */ const setupMultiTenantPrivacyEntities = function(callback) { // Create the tenants and users - TestsUtil.setupMultiTenantPrivacyEntities( - (publicTenant, publicTenant1, privateTenant, privateTenant1) => { - // Create the discussions. - _setupTenant(publicTenant, () => { - _setupTenant(publicTenant1, () => { - _setupTenant(privateTenant, () => { - _setupTenant(privateTenant1, () => { - return callback(publicTenant, publicTenant1, privateTenant, privateTenant1); - }); + TestsUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1, privateTenant, privateTenant1) => { + // Create the discussions. + _setupTenant(publicTenant, () => { + _setupTenant(publicTenant1, () => { + _setupTenant(privateTenant, () => { + _setupTenant(privateTenant1, () => { + return callback(publicTenant, publicTenant1, privateTenant, privateTenant1); }); }); }); - } - ); + }); + }); }; /** @@ -80,15 +78,12 @@ const setupMultiTenantPrivacyEntities = function(callback) { * @api private */ const _setupTenant = function(tenant, callback) { - _createMultiPrivacyDiscussions( - tenant.adminRestContext, - (publicDiscussion, loggedinDiscussion, privateDiscussion) => { - tenant.publicDiscussion = publicDiscussion; - tenant.loggedinDiscussion = loggedinDiscussion; - tenant.privateDiscussion = privateDiscussion; - callback(); - } - ); + _createMultiPrivacyDiscussions(tenant.adminRestContext, (publicDiscussion, loggedinDiscussion, privateDiscussion) => { + tenant.publicDiscussion = publicDiscussion; + tenant.loggedinDiscussion = loggedinDiscussion; + tenant.privateDiscussion = privateDiscussion; + callback(); + }); }; /** @@ -185,25 +180,16 @@ const assertCreateDiscussionSucceeds = function( // Ensure the members have the expected roles getAllDiscussionMembers(restContext, discussion.id, null, result => { - AuthzTestUtil.assertMemberRolesEquals( - {}, - roleChanges, - AuthzTestUtil.getMemberRolesFromResults(result) - ); - - AuthzTestUtil.assertGetInvitationsSucceeds( - restContext, - 'discussion', - discussion.id, - result => { - AuthzTestUtil.assertEmailRolesEquals( - {}, - roleChanges, - AuthzTestUtil.getEmailRolesFromResults(result.results) - ); - return callback(discussion); - } - ); + AuthzTestUtil.assertMemberRolesEquals({}, roleChanges, AuthzTestUtil.getMemberRolesFromResults(result)); + + AuthzTestUtil.assertGetInvitationsSucceeds(restContext, 'discussion', discussion.id, result => { + AuthzTestUtil.assertEmailRolesEquals( + {}, + roleChanges, + AuthzTestUtil.getEmailRolesFromResults(result.results) + ); + return callback(discussion); + }); }); } ); @@ -330,57 +316,42 @@ const assertShareDiscussionSucceeds = function( getAllDiscussionMembers(managerRestContext, discussionId, null, result => { const memberRolesBefore = AuthzTestUtil.getMemberRolesFromResults(result); - AuthzTestUtil.assertGetInvitationsSucceeds( - managerRestContext, - 'discussion', - discussionId, - result => { - const emailRolesBefore = AuthzTestUtil.getEmailRolesFromResults(result.results); - - // Build a role update object that represents the change that should occur in the share - // operation - const roleChange = {}; - _.each(targetIds, targetId => { - if (!memberRolesBefore[targetId] && !emailRolesBefore[targetId]) { - roleChange[targetId] = 'member'; - } - }); + AuthzTestUtil.assertGetInvitationsSucceeds(managerRestContext, 'discussion', discussionId, result => { + const emailRolesBefore = AuthzTestUtil.getEmailRolesFromResults(result.results); - // Perform the discussion share - RestAPI.Discussions.shareDiscussion(actorRestContext, discussionId, targetIds, err => { - assert.ok(!err); + // Build a role update object that represents the change that should occur in the share + // operation + const roleChange = {}; + _.each(targetIds, targetId => { + if (!memberRolesBefore[targetId] && !emailRolesBefore[targetId]) { + roleChange[targetId] = 'member'; + } + }); - // Ensure the members and invitations had the expected updates - AuthzTestUtil.assertGetInvitationsSucceeds( - managerRestContext, - 'discussion', - discussionId, - result => { - AuthzTestUtil.assertEmailRolesEquals( - emailRolesBefore, - roleChange, - AuthzTestUtil.getEmailRolesFromResults(result.results) - ); + // Perform the discussion share + RestAPI.Discussions.shareDiscussion(actorRestContext, discussionId, targetIds, err => { + assert.ok(!err); - getAllDiscussionMembers( - managerRestContext, - discussionId, - null, - membersAfterUpdate => { - AuthzTestUtil.assertMemberRolesEquals( - memberRolesBefore, - roleChange, - AuthzTestUtil.getMemberRolesFromResults(membersAfterUpdate) - ); - - return callback(); - } - ); - } + // Ensure the members and invitations had the expected updates + AuthzTestUtil.assertGetInvitationsSucceeds(managerRestContext, 'discussion', discussionId, result => { + AuthzTestUtil.assertEmailRolesEquals( + emailRolesBefore, + roleChange, + AuthzTestUtil.getEmailRolesFromResults(result.results) ); + + getAllDiscussionMembers(managerRestContext, discussionId, null, membersAfterUpdate => { + AuthzTestUtil.assertMemberRolesEquals( + memberRolesBefore, + roleChange, + AuthzTestUtil.getMemberRolesFromResults(membersAfterUpdate) + ); + + return callback(); + }); }); - } - ); + }); + }); }); }; @@ -407,51 +378,36 @@ const assertShareDiscussionFails = function( getAllDiscussionMembers(managerRestContext, discussionId, null, result => { const memberRolesBefore = AuthzTestUtil.getMemberRolesFromResults(result); - AuthzTestUtil.assertGetInvitationsSucceeds( - managerRestContext, - 'discussion', - discussionId, - result => { - const emailRolesBefore = AuthzTestUtil.getEmailRolesFromResults(result.results); - - // Perform the discussion share - RestAPI.Discussions.shareDiscussion(actorRestContext, discussionId, targetIds, err => { - assert.ok(err); - assert.strictEqual(err.code, httpCode); - - const delta = {}; - - // Ensure the members and invitations had the expected updates - AuthzTestUtil.assertGetInvitationsSucceeds( - managerRestContext, - 'discussion', - discussionId, - result => { - AuthzTestUtil.assertEmailRolesEquals( - emailRolesBefore, - delta, - AuthzTestUtil.getEmailRolesFromResults(result.results) - ); + AuthzTestUtil.assertGetInvitationsSucceeds(managerRestContext, 'discussion', discussionId, result => { + const emailRolesBefore = AuthzTestUtil.getEmailRolesFromResults(result.results); - getAllDiscussionMembers( - managerRestContext, - discussionId, - null, - membersAfterUpdate => { - AuthzTestUtil.assertMemberRolesEquals( - memberRolesBefore, - delta, - AuthzTestUtil.getMemberRolesFromResults(membersAfterUpdate) - ); - - return callback(); - } - ); - } + // Perform the discussion share + RestAPI.Discussions.shareDiscussion(actorRestContext, discussionId, targetIds, err => { + assert.ok(err); + assert.strictEqual(err.code, httpCode); + + const delta = {}; + + // Ensure the members and invitations had the expected updates + AuthzTestUtil.assertGetInvitationsSucceeds(managerRestContext, 'discussion', discussionId, result => { + AuthzTestUtil.assertEmailRolesEquals( + emailRolesBefore, + delta, + AuthzTestUtil.getEmailRolesFromResults(result.results) ); + + getAllDiscussionMembers(managerRestContext, discussionId, null, membersAfterUpdate => { + AuthzTestUtil.assertMemberRolesEquals( + memberRolesBefore, + delta, + AuthzTestUtil.getMemberRolesFromResults(membersAfterUpdate) + ); + + return callback(); + }); }); - } - ); + }); + }); }); }; @@ -475,56 +431,36 @@ const assertUpdateDiscussionMembersSucceeds = function( getAllDiscussionMembers(managerRestContext, discussionId, null, result => { const memberRolesBefore = AuthzTestUtil.getMemberRolesFromResults(result); - AuthzTestUtil.assertGetInvitationsSucceeds( - managerRestContext, - 'discussion', - discussionId, - result => { - const emailRolesBefore = AuthzTestUtil.getEmailRolesFromResults(result.results); - - RestAPI.Discussions.updateDiscussionMembers( - actorRestContext, - discussionId, - updates, - err => { - assert.ok(!err); - // Wait for library and search to be updated before continuing - LibraryAPI.Index.whenUpdatesComplete(() => { - SearchTestUtil.whenIndexingComplete(() => { - // Ensure the members and invitations had the expected updates - AuthzTestUtil.assertGetInvitationsSucceeds( - managerRestContext, - 'discussion', - discussionId, - result => { - AuthzTestUtil.assertEmailRolesEquals( - emailRolesBefore, - updates, - AuthzTestUtil.getEmailRolesFromResults(result.results) - ); - - getAllDiscussionMembers( - managerRestContext, - discussionId, - null, - membersAfterUpdate => { - AuthzTestUtil.assertMemberRolesEquals( - memberRolesBefore, - updates, - AuthzTestUtil.getMemberRolesFromResults(membersAfterUpdate) - ); - - return callback(); - } - ); - } + AuthzTestUtil.assertGetInvitationsSucceeds(managerRestContext, 'discussion', discussionId, result => { + const emailRolesBefore = AuthzTestUtil.getEmailRolesFromResults(result.results); + + RestAPI.Discussions.updateDiscussionMembers(actorRestContext, discussionId, updates, err => { + assert.ok(!err); + // Wait for library and search to be updated before continuing + LibraryAPI.Index.whenUpdatesComplete(() => { + SearchTestUtil.whenIndexingComplete(() => { + // Ensure the members and invitations had the expected updates + AuthzTestUtil.assertGetInvitationsSucceeds(managerRestContext, 'discussion', discussionId, result => { + AuthzTestUtil.assertEmailRolesEquals( + emailRolesBefore, + updates, + AuthzTestUtil.getEmailRolesFromResults(result.results) + ); + + getAllDiscussionMembers(managerRestContext, discussionId, null, membersAfterUpdate => { + AuthzTestUtil.assertMemberRolesEquals( + memberRolesBefore, + updates, + AuthzTestUtil.getMemberRolesFromResults(membersAfterUpdate) ); + + return callback(); }); }); - } - ); - } - ); + }); + }); + }); + }); }); }; @@ -551,60 +487,40 @@ const assertUpdateDiscussionMembersFails = function( getAllDiscussionMembers(managerRestContext, discussionId, null, result => { const memberRolesBefore = AuthzTestUtil.getMemberRolesFromResults(result); - AuthzTestUtil.assertGetInvitationsSucceeds( - managerRestContext, - 'discussion', - discussionId, - result => { - const emailRolesBefore = AuthzTestUtil.getEmailRolesFromResults(result.results); - - RestAPI.Discussions.updateDiscussionMembers( - actorRestContext, - discussionId, - updates, - err => { - assert.ok(err); - assert.strictEqual(err.code, httpCode); - - // Wait for library and search to be udpated before continuing - LibraryAPI.Index.whenUpdatesComplete(() => { - SearchTestUtil.whenIndexingComplete(() => { - const delta = {}; - - // Ensure the members and invitations had the expected updates - AuthzTestUtil.assertGetInvitationsSucceeds( - managerRestContext, - 'discussion', - discussionId, - result => { - AuthzTestUtil.assertEmailRolesEquals( - emailRolesBefore, - delta, - AuthzTestUtil.getEmailRolesFromResults(result.results) - ); - - getAllDiscussionMembers( - managerRestContext, - discussionId, - null, - membersAfterUpdate => { - AuthzTestUtil.assertMemberRolesEquals( - memberRolesBefore, - delta, - AuthzTestUtil.getMemberRolesFromResults(membersAfterUpdate) - ); - - return callback(); - } - ); - } + AuthzTestUtil.assertGetInvitationsSucceeds(managerRestContext, 'discussion', discussionId, result => { + const emailRolesBefore = AuthzTestUtil.getEmailRolesFromResults(result.results); + + RestAPI.Discussions.updateDiscussionMembers(actorRestContext, discussionId, updates, err => { + assert.ok(err); + assert.strictEqual(err.code, httpCode); + + // Wait for library and search to be udpated before continuing + LibraryAPI.Index.whenUpdatesComplete(() => { + SearchTestUtil.whenIndexingComplete(() => { + const delta = {}; + + // Ensure the members and invitations had the expected updates + AuthzTestUtil.assertGetInvitationsSucceeds(managerRestContext, 'discussion', discussionId, result => { + AuthzTestUtil.assertEmailRolesEquals( + emailRolesBefore, + delta, + AuthzTestUtil.getEmailRolesFromResults(result.results) + ); + + getAllDiscussionMembers(managerRestContext, discussionId, null, membersAfterUpdate => { + AuthzTestUtil.assertMemberRolesEquals( + memberRolesBefore, + delta, + AuthzTestUtil.getMemberRolesFromResults(membersAfterUpdate) ); + + return callback(); }); }); - } - ); - } - ); + }); + }); + }); + }); }); }; @@ -620,15 +536,7 @@ const assertUpdateDiscussionMembersFails = function( * @param {Object[][]} callback.responses The raw response objects for each page request that was made to get the discussion members library * @throws {AssertionError} Thrown if an error occurrs while paging through the discussion members library */ -const getAllDiscussionMembers = function( - restContext, - discussionId, - opts, - callback, - _members, - _responses, - _nextToken -) { +const getAllDiscussionMembers = function(restContext, discussionId, opts, callback, _members, _responses, _nextToken) { _members = _members || []; _responses = _responses || []; if (_nextToken === null) { @@ -637,25 +545,19 @@ const getAllDiscussionMembers = function( opts = opts || {}; opts.batchSize = opts.batchSize || 25; - RestAPI.Discussions.getDiscussionMembers( - restContext, - discussionId, - _nextToken, - opts.batchSize, - (err, result) => { - assert.ok(!err); - _responses.push(result); - return getAllDiscussionMembers( - restContext, - discussionId, - opts, - callback, - _.union(_members, result.results), - _responses, - result.nextToken - ); - } - ); + RestAPI.Discussions.getDiscussionMembers(restContext, discussionId, _nextToken, opts.batchSize, (err, result) => { + assert.ok(!err); + _responses.push(result); + return getAllDiscussionMembers( + restContext, + discussionId, + opts, + callback, + _.union(_members, result.results), + _responses, + result.nextToken + ); + }); }; /** @@ -718,19 +620,13 @@ const assertGetAllDiscussionsLibrarySucceeds = function( */ const assertGetDiscussionsLibrarySucceeds = function(restContext, principalId, opts, callback) { opts = opts || {}; - RestAPI.Discussions.getDiscussionsLibrary( - restContext, - principalId, - opts.start, - opts.limit, - (err, result) => { - assert.ok(!err); - return callback(result); - } - ); + RestAPI.Discussions.getDiscussionsLibrary(restContext, principalId, opts.start, opts.limit, (err, result) => { + assert.ok(!err); + return callback(result); + }); }; -module.exports = { +export { setupMultiTenantPrivacyEntities, assertCreateDiscussionSucceeds, assertCreateDiscussionFails, diff --git a/packages/oae-discussions/tests/test-activity.js b/packages/oae-discussions/tests/test-activity.js index 819c834cae..d20dfb5350 100644 --- a/packages/oae-discussions/tests/test-activity.js +++ b/packages/oae-discussions/tests/test-activity.js @@ -14,19 +14,15 @@ */ /* eslint-disable no-unused-vars */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const PreviewConstants = require('oae-preview-processor/lib/constants'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const Sanitization = require('oae-util/lib/sanitization'); -const TestsUtil = require('oae-tests'); - -const ActivityTestsUtil = require('oae-activity/lib/test/util'); -const EmailTestsUtil = require('oae-email/lib/test/util'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as ActivityTestsUtil from 'oae-activity/lib/test/util'; +import * as EmailTestsUtil from 'oae-email/lib/test/util'; describe('Discussion Activity', () => { // Rest contexts that can be used performing rest requests @@ -157,10 +153,7 @@ describe('Discussion Activity', () => { (err, activityStream) => { assert.ok(!err); const entity = activityStream.items[0]; - assert.strictEqual( - entity['oae:activityType'], - 'discussion-update-visibility' - ); + assert.strictEqual(entity['oae:activityType'], 'discussion-update-visibility'); assert.strictEqual(entity.verb, 'update'); callback(); } @@ -260,12 +253,9 @@ describe('Discussion Activity', () => { assert.strictEqual(entity.object.objectType, 'collection'); assert.ok(entity.object['oae:collection']); assert.strictEqual(entity.object['oae:collection'].length, 2); - const originalMessage = _.find( - entity.object['oae:collection'], - activityMessage => { - return activityMessage['oae:id'] === message.id; - } - ); + const originalMessage = _.find(entity.object['oae:collection'], activityMessage => { + return activityMessage['oae:id'] === message.id; + }); assert.ok(originalMessage); assert.strictEqual(originalMessage['oae:id'], message.id); assert.strictEqual(originalMessage.content, message.body); @@ -275,23 +265,14 @@ describe('Discussion Activity', () => { global.oaeTests.tenants.cam.alias ); - const reply = _.find( - entity.object['oae:collection'], - activityMessage => { - return activityMessage['oae:id'] === nicosMessage.id; - } - ); + const reply = _.find(entity.object['oae:collection'], activityMessage => { + return activityMessage['oae:id'] === nicosMessage.id; + }); assert.ok(reply); assert.strictEqual(reply['oae:id'], nicosMessage.id); - assert.strictEqual( - reply['oae:messageBoxId'], - nicosMessage.messageBoxId - ); + assert.strictEqual(reply['oae:messageBoxId'], nicosMessage.messageBoxId); assert.strictEqual(reply['oae:threadKey'], nicosMessage.threadKey); - assert.strictEqual( - reply['oae:tenant'].alias, - global.oaeTests.tenants.cam.alias - ); + assert.strictEqual(reply['oae:tenant'].alias, global.oaeTests.tenants.cam.alias); assert.strictEqual(reply.content, nicosMessage.body); assert.strictEqual(reply.published, nicosMessage.created); assert.strictEqual(reply.author['oae:id'], nico.user.id); @@ -300,12 +281,9 @@ describe('Discussion Activity', () => { // Verify both actors are present assert.strictEqual(entity.actor.objectType, 'collection'); - const simonEntity = _.find( - entity.actor['oae:collection'], - userEntity => { - return userEntity['oae:id'] === simon.user.id; - } - ); + const simonEntity = _.find(entity.actor['oae:collection'], userEntity => { + return userEntity['oae:id'] === simon.user.id; + }); assert.ok(simonEntity); assert.strictEqual(simonEntity['oae:id'], simon.user.id); assert.strictEqual( @@ -316,12 +294,9 @@ describe('Discussion Activity', () => { AuthzUtil.getResourceFromId(simon.user.id).resourceId ); - const nicoEntity = _.find( - entity.actor['oae:collection'], - userEntity => { - return userEntity['oae:id'] === nico.user.id; - } - ); + const nicoEntity = _.find(entity.actor['oae:collection'], userEntity => { + return userEntity['oae:id'] === nico.user.id; + }); assert.ok(nicoEntity); assert.strictEqual(nicoEntity['oae:id'], nico.user.id); assert.strictEqual( @@ -353,113 +328,81 @@ describe('Discussion Activity', () => { * Test that verifies that a message activity is routed to the managers and recent contributers their notification stream of a private discussion item */ it('verify message activity is routed to the managers and recent contributers notification stream of a private discussion', callback => { - TestsUtil.generateTestUsers( - camAdminRestContext, - 4, - (err, users, simon, nico, bert, stuart) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 4, (err, users, simon, nico, bert, stuart) => { + assert.ok(!err); - RestAPI.Discussions.createDiscussion( - simon.restContext, - 'Something something discussworthy', - 'Start discussing this sweet topic', - 'private', - [nico.user.id], - [bert.user.id, stuart.user.id], - (err, discussion) => { + RestAPI.Discussions.createDiscussion( + simon.restContext, + 'Something something discussworthy', + 'Start discussing this sweet topic', + 'private', + [nico.user.id], + [bert.user.id, stuart.user.id], + (err, discussion) => { + assert.ok(!err); + + RestAPI.Discussions.createMessage(bert.restContext, discussion.id, 'Message A', null, (err, message) => { assert.ok(!err); - RestAPI.Discussions.createMessage( - bert.restContext, - discussion.id, - 'Message A', - null, - (err, message) => { + // Assert that the managers got it + ActivityTestsUtil.collectAndGetNotificationStream(simon.restContext, null, (err, activityStream) => { + assert.ok(!err); + assert.ok( + _.find(activityStream.items, activity => { + return activity['oae:activityType'] === 'discussion-message'; + }) + ); + + ActivityTestsUtil.collectAndGetNotificationStream(nico.restContext, null, (err, activityStream) => { assert.ok(!err); + assert.ok( + _.find(activityStream.items, activity => { + return activity['oae:activityType'] === 'discussion-message'; + }) + ); - // Assert that the managers got it - ActivityTestsUtil.collectAndGetNotificationStream( - simon.restContext, + // Create another message and assert that both the managers and the recent contributers get a notification + RestAPI.Discussions.createMessage( + nico.restContext, + discussion.id, + 'Message A', null, - (err, activityStream) => { + (err, message) => { assert.ok(!err); - assert.ok( - _.find(activityStream.items, activity => { - return activity['oae:activityType'] === 'discussion-message'; - }) - ); + // Because Bert made a message previously, he should get a notification as well ActivityTestsUtil.collectAndGetNotificationStream( - nico.restContext, + bert.restContext, null, (err, activityStream) => { assert.ok(!err); - assert.ok( - _.find(activityStream.items, activity => { - return activity['oae:activityType'] === 'discussion-message'; - }) - ); + const messageActivities = _.filter(activityStream.items, activity => { + return activity['oae:activityType'] === 'discussion-message'; + }); + assert.ok(messageActivities.length, 2); - // Create another message and assert that both the managers and the recent contributers get a notification - RestAPI.Discussions.createMessage( + // Sanity-check that the managers got it as well + ActivityTestsUtil.collectAndGetNotificationStream( nico.restContext, - discussion.id, - 'Message A', null, - (err, message) => { + (err, activityStream) => { assert.ok(!err); + const messageActivities = _.filter(activityStream.items, activity => { + return activity['oae:activityType'] === 'discussion-message'; + }); + assert.ok(messageActivities.length, 2); - // Because Bert made a message previously, he should get a notification as well ActivityTestsUtil.collectAndGetNotificationStream( - bert.restContext, + simon.restContext, null, (err, activityStream) => { assert.ok(!err); - const messageActivities = _.filter( - activityStream.items, - activity => { - return activity['oae:activityType'] === 'discussion-message'; - } - ); + const messageActivities = _.filter(activityStream.items, activity => { + return activity['oae:activityType'] === 'discussion-message'; + }); assert.ok(messageActivities.length, 2); - // Sanity-check that the managers got it as well - ActivityTestsUtil.collectAndGetNotificationStream( - nico.restContext, - null, - (err, activityStream) => { - assert.ok(!err); - const messageActivities = _.filter( - activityStream.items, - activity => { - return ( - activity['oae:activityType'] === 'discussion-message' - ); - } - ); - assert.ok(messageActivities.length, 2); - - ActivityTestsUtil.collectAndGetNotificationStream( - simon.restContext, - null, - (err, activityStream) => { - assert.ok(!err); - const messageActivities = _.filter( - activityStream.items, - activity => { - return ( - activity['oae:activityType'] === - 'discussion-message' - ); - } - ); - assert.ok(messageActivities.length, 2); - - return callback(); - } - ); - } - ); + return callback(); } ); } @@ -468,12 +411,12 @@ describe('Discussion Activity', () => { ); } ); - } - ); - } - ); - } - ); + }); + }); + }); + } + ); + }); }); }); @@ -551,34 +494,29 @@ describe('Discussion Activity', () => { assert.ok(discussion); // Simon shares the discussion with nicolaas - RestAPI.Discussions.shareDiscussion( - simon.restContext, - discussion.id, - [nico.user.id], - err => { - assert.ok(!err); + RestAPI.Discussions.shareDiscussion(simon.restContext, discussion.id, [nico.user.id], err => { + assert.ok(!err); - // Collect the activities - ActivityTestsUtil.collectAndGetActivityStream( - simon.restContext, - simon.user.id, - null, - (err, activityStream) => { - assert.ok(!err); + // Collect the activities + ActivityTestsUtil.collectAndGetActivityStream( + simon.restContext, + simon.user.id, + null, + (err, activityStream) => { + assert.ok(!err); - // Verify the discussion-share activity is the newest one in the feed - const activity = activityStream.items[0]; - assert.ok(activity); - assert.strictEqual(activity['oae:activityType'], 'discussion-share'); - assert.strictEqual(activity.actor['oae:id'], simon.user.id); - assert.strictEqual(activity.object['oae:id'], discussion.id); - assert.strictEqual(activity.target['oae:id'], nico.user.id); + // Verify the discussion-share activity is the newest one in the feed + const activity = activityStream.items[0]; + assert.ok(activity); + assert.strictEqual(activity['oae:activityType'], 'discussion-share'); + assert.strictEqual(activity.actor['oae:id'], simon.user.id); + assert.strictEqual(activity.object['oae:id'], discussion.id); + assert.strictEqual(activity.target['oae:id'], nico.user.id); - return callback(); - } - ); - } - ); + return callback(); + } + ); + }); } ); }); @@ -607,34 +545,29 @@ describe('Discussion Activity', () => { memberUpdates[branden.user.id] = 'member'; // Simon shares the discussion with Branden - RestAPI.Discussions.updateDiscussionMembers( - simon.restContext, - discussion.id, - memberUpdates, - err => { - assert.ok(!err); + RestAPI.Discussions.updateDiscussionMembers(simon.restContext, discussion.id, memberUpdates, err => { + assert.ok(!err); - // Collect the activities - ActivityTestsUtil.collectAndGetActivityStream( - simon.restContext, - simon.user.id, - null, - (err, activityStream) => { - assert.ok(!err); + // Collect the activities + ActivityTestsUtil.collectAndGetActivityStream( + simon.restContext, + simon.user.id, + null, + (err, activityStream) => { + assert.ok(!err); - // Verify the discussion-share activity is the newest one in the feed - const activity = activityStream.items[0]; - assert.ok(activity); - assert.strictEqual(activity['oae:activityType'], 'discussion-share'); - assert.strictEqual(activity.actor['oae:id'], simon.user.id); - assert.strictEqual(activity.object['oae:id'], discussion.id); - assert.strictEqual(activity.target['oae:id'], branden.user.id); + // Verify the discussion-share activity is the newest one in the feed + const activity = activityStream.items[0]; + assert.ok(activity); + assert.strictEqual(activity['oae:activityType'], 'discussion-share'); + assert.strictEqual(activity.actor['oae:id'], simon.user.id); + assert.strictEqual(activity.object['oae:id'], discussion.id); + assert.strictEqual(activity.target['oae:id'], branden.user.id); - return callback(); - } - ); - } - ); + return callback(); + } + ); + }); } ); }); @@ -662,33 +595,28 @@ describe('Discussion Activity', () => { // Simon promotes Branden to manager const memberUpdates = {}; memberUpdates[branden.user.id] = 'manager'; - RestAPI.Discussions.updateDiscussionMembers( - simon.restContext, - discussion.id, - memberUpdates, - err => { - assert.ok(!err); + RestAPI.Discussions.updateDiscussionMembers(simon.restContext, discussion.id, memberUpdates, err => { + assert.ok(!err); - // Verify the discussion-update-member-role activity is present - ActivityTestsUtil.collectAndGetActivityStream( - simon.restContext, - simon.user.id, - null, - (err, activityStream) => { - assert.ok(!err); - ActivityTestsUtil.assertActivity( - activityStream.items[0], - 'discussion-update-member-role', - 'update', - simon.user.id, - branden.user.id, - discussion.id - ); - return callback(); - } - ); - } - ); + // Verify the discussion-update-member-role activity is present + ActivityTestsUtil.collectAndGetActivityStream( + simon.restContext, + simon.user.id, + null, + (err, activityStream) => { + assert.ok(!err); + ActivityTestsUtil.assertActivity( + activityStream.items[0], + 'discussion-update-member-role', + 'update', + simon.user.id, + branden.user.id, + discussion.id + ); + return callback(); + } + ); + }); } ); }); @@ -715,33 +643,28 @@ describe('Discussion Activity', () => { assert.ok(discussion); // Nicolaas adds the discussion to his library - RestAPI.Discussions.shareDiscussion( - nico.restContext, - discussion.id, - [nico.user.id], - err => { - assert.ok(!err); + RestAPI.Discussions.shareDiscussion(nico.restContext, discussion.id, [nico.user.id], err => { + assert.ok(!err); - // Collect the activities - ActivityTestsUtil.collectAndGetActivityStream( - nico.restContext, - nico.user.id, - null, - (err, activityStream) => { - assert.ok(!err); + // Collect the activities + ActivityTestsUtil.collectAndGetActivityStream( + nico.restContext, + nico.user.id, + null, + (err, activityStream) => { + assert.ok(!err); - // Verify the discussion-share activity is the newest one in the feed - const activity = activityStream.items[0]; - assert.ok(activity); - assert.strictEqual(activity['oae:activityType'], 'discussion-add-to-library'); - assert.strictEqual(activity.actor['oae:id'], nico.user.id); - assert.strictEqual(activity.object['oae:id'], discussion.id); + // Verify the discussion-share activity is the newest one in the feed + const activity = activityStream.items[0]; + assert.ok(activity); + assert.strictEqual(activity['oae:activityType'], 'discussion-add-to-library'); + assert.strictEqual(activity.actor['oae:id'], nico.user.id); + assert.strictEqual(activity.object['oae:id'], discussion.id); - return callback(); - } - ); - } - ); + return callback(); + } + ); + }); } ); }); @@ -754,114 +677,96 @@ describe('Discussion Activity', () => { * are appropriately scrubbed. */ it('verify discussion message email and privacy', callback => { - TestsUtil.generateTestUsers( - camAdminRestContext, - 3, - (err, users, mrvisser, simong, nicolaas) => { - assert.ok(!err); - - // Simon is private and mrvisser is public - const simongUpdate = { - visibility: 'private', - publicAlias: 'swappedFromPublicAlias' - }; - - // Update Simon - PrincipalsTestUtil.assertUpdateUserSucceeds( - simong.restContext, - simong.user.id, - simongUpdate, - () => { - // Create the discussion - RestAPI.Discussions.createDiscussion( - mrvisser.restContext, - 'A talk', - 'about computers', - 'public', - [], - [], - (err, discussion) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 3, (err, users, mrvisser, simong, nicolaas) => { + assert.ok(!err); - // Post a new message - RestAPI.Discussions.createMessage( - simong.restContext, - discussion.id, - '\n\nWould read again', - null, - (err, simongMessage) => { - assert.ok(!err); + // Simon is private and mrvisser is public + const simongUpdate = { + visibility: 'private', + publicAlias: 'swappedFromPublicAlias' + }; - EmailTestsUtil.collectAndFetchAllEmails(emails => { - // There should be exactly one email, the one sent to mrvisser (manager of discussion receives discussion-message notification) - assert.strictEqual(emails.length, 1); - - const stringEmail = JSON.stringify(emails[0]); - const email = emails[0]; - - // Sanity check that the email is to mrvisser - assert.strictEqual(email.to[0].address, mrvisser.user.email); - - // Ensure that the subject of the email contains the poster's name - assert.notStrictEqual(email.subject.indexOf('swappedFromPublicAlias'), -1); - - // Ensure some data expected to be in the email is there - assert.notStrictEqual( - stringEmail.indexOf(simong.restContext.hostHeader), - -1 - ); - assert.notStrictEqual(stringEmail.indexOf(discussion.profilePath), -1); - assert.notStrictEqual(stringEmail.indexOf(discussion.displayName), -1); - - // Ensure simong's private info is nowhere to be found - assert.strictEqual(stringEmail.indexOf(simong.user.displayName), -1); - assert.strictEqual(stringEmail.indexOf(simong.user.email), -1); - assert.strictEqual(stringEmail.indexOf(simong.user.locale), -1); - - // The email should contain the public alias - assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); - - // The message should have escaped the HTML content in the original message - assert.strictEqual( - stringEmail.indexOf(''), - -1 - ); - - // The new line characters should've been converted into paragraphs - assert.notStrictEqual(stringEmail.indexOf('Would read again

'), -1); - - // Send a message as nicolaas and ensure the recent commenter, simong receives an email about it - RestAPI.Discussions.createMessage( - nicolaas.restContext, - discussion.id, - 'I have a computer, too', - null, - (err, nicolaasMessage) => { - assert.ok(!err); - - EmailTestsUtil.collectAndFetchAllEmails(emails => { - // There should be 2 emails this time, one to the manager and one to the recent commenter, simong - assert.strictEqual(emails.length, 2); - - const emailAddresses = [ - emails[0].to[0].address, - emails[1].to[0].address - ]; - assert.ok(_.contains(emailAddresses, simong.user.email)); - assert.ok(_.contains(emailAddresses, mrvisser.user.email)); - return callback(); - }); - } - ); - }); - } - ); + // Update Simon + PrincipalsTestUtil.assertUpdateUserSucceeds(simong.restContext, simong.user.id, simongUpdate, () => { + // Create the discussion + RestAPI.Discussions.createDiscussion( + mrvisser.restContext, + 'A talk', + 'about computers', + 'public', + [], + [], + (err, discussion) => { + assert.ok(!err); + + // Post a new message + RestAPI.Discussions.createMessage( + simong.restContext, + discussion.id, + '\n\nWould read again', + null, + (err, simongMessage) => { + assert.ok(!err); + + EmailTestsUtil.collectAndFetchAllEmails(emails => { + // There should be exactly one email, the one sent to mrvisser (manager of discussion receives discussion-message notification) + assert.strictEqual(emails.length, 1); + + const stringEmail = JSON.stringify(emails[0]); + const email = emails[0]; + + // Sanity check that the email is to mrvisser + assert.strictEqual(email.to[0].address, mrvisser.user.email); + + // Ensure that the subject of the email contains the poster's name + assert.notStrictEqual(email.subject.indexOf('swappedFromPublicAlias'), -1); + + // Ensure some data expected to be in the email is there + assert.notStrictEqual(stringEmail.indexOf(simong.restContext.hostHeader), -1); + assert.notStrictEqual(stringEmail.indexOf(discussion.profilePath), -1); + assert.notStrictEqual(stringEmail.indexOf(discussion.displayName), -1); + + // Ensure simong's private info is nowhere to be found + assert.strictEqual(stringEmail.indexOf(simong.user.displayName), -1); + assert.strictEqual(stringEmail.indexOf(simong.user.email), -1); + assert.strictEqual(stringEmail.indexOf(simong.user.locale), -1); + + // The email should contain the public alias + assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); + + // The message should have escaped the HTML content in the original message + assert.strictEqual(stringEmail.indexOf(''), -1); + + // The new line characters should've been converted into paragraphs + assert.notStrictEqual(stringEmail.indexOf('Would read again

'), -1); + + // Send a message as nicolaas and ensure the recent commenter, simong receives an email about it + RestAPI.Discussions.createMessage( + nicolaas.restContext, + discussion.id, + 'I have a computer, too', + null, + (err, nicolaasMessage) => { + assert.ok(!err); + + EmailTestsUtil.collectAndFetchAllEmails(emails => { + // There should be 2 emails this time, one to the manager and one to the recent commenter, simong + assert.strictEqual(emails.length, 2); + + const emailAddresses = [emails[0].to[0].address, emails[1].to[0].address]; + assert.ok(_.contains(emailAddresses, simong.user.email)); + assert.ok(_.contains(emailAddresses, mrvisser.user.email)); + return callback(); + }); + } + ); + }); } ); } ); - } - ); + }); + }); }); /** @@ -879,52 +784,47 @@ describe('Discussion Activity', () => { }; // Update Simon - PrincipalsTestUtil.assertUpdateUserSucceeds( - simong.restContext, - simong.user.id, - simongUpdate, - () => { - // Create the link, sharing it with mrvisser during the creation step. We will ensure he gets an email about it - RestAPI.Discussions.createDiscussion( - simong.restContext, - 'A talk', - 'not about computers', - 'public', - [], - [mrvisser.user.id], - (err, discussion) => { - assert.ok(!err); + PrincipalsTestUtil.assertUpdateUserSucceeds(simong.restContext, simong.user.id, simongUpdate, () => { + // Create the link, sharing it with mrvisser during the creation step. We will ensure he gets an email about it + RestAPI.Discussions.createDiscussion( + simong.restContext, + 'A talk', + 'not about computers', + 'public', + [], + [mrvisser.user.id], + (err, discussion) => { + assert.ok(!err); - // Mrvisser should get an email, with simong's information scrubbed - EmailTestsUtil.collectAndFetchAllEmails(emails => { - // There should be exactly one email, the one sent to mrvisser - assert.strictEqual(emails.length, 1); + // Mrvisser should get an email, with simong's information scrubbed + EmailTestsUtil.collectAndFetchAllEmails(emails => { + // There should be exactly one email, the one sent to mrvisser + assert.strictEqual(emails.length, 1); - const stringEmail = JSON.stringify(emails[0]); - const email = emails[0]; + const stringEmail = JSON.stringify(emails[0]); + const email = emails[0]; - // Sanity check that the email is to mrvisser - assert.strictEqual(email.to[0].address, mrvisser.user.email); + // Sanity check that the email is to mrvisser + assert.strictEqual(email.to[0].address, mrvisser.user.email); - // Ensure some data expected to be in the email is there - assert.notStrictEqual(stringEmail.indexOf(simong.restContext.hostHeader), -1); - assert.notStrictEqual(stringEmail.indexOf(discussion.profilePath), -1); - assert.notStrictEqual(stringEmail.indexOf(discussion.displayName), -1); + // Ensure some data expected to be in the email is there + assert.notStrictEqual(stringEmail.indexOf(simong.restContext.hostHeader), -1); + assert.notStrictEqual(stringEmail.indexOf(discussion.profilePath), -1); + assert.notStrictEqual(stringEmail.indexOf(discussion.displayName), -1); - // Ensure simong's private info is nowhere to be found - assert.strictEqual(stringEmail.indexOf(simong.user.displayName), -1); - assert.strictEqual(stringEmail.indexOf(simong.user.email), -1); - assert.strictEqual(stringEmail.indexOf(simong.user.locale), -1); + // Ensure simong's private info is nowhere to be found + assert.strictEqual(stringEmail.indexOf(simong.user.displayName), -1); + assert.strictEqual(stringEmail.indexOf(simong.user.email), -1); + assert.strictEqual(stringEmail.indexOf(simong.user.locale), -1); - // The email should contain the public alias - assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); + // The email should contain the public alias + assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); - return callback(); - }); - } - ); - } - ); + return callback(); + }); + } + ); + }); }); }); @@ -943,66 +843,53 @@ describe('Discussion Activity', () => { }; // Update Simon - PrincipalsTestUtil.assertUpdateUserSucceeds( - simong.restContext, - simong.user.id, - simongUpdate, - () => { - // Create the link, then share it with mrvisser. We will ensure that mrvisser gets the email about the share - RestAPI.Discussions.createDiscussion( - simong.restContext, - 'A talk', - 'about the moon', - 'public', - [], - [], - (err, discussion) => { - assert.ok(!err); + PrincipalsTestUtil.assertUpdateUserSucceeds(simong.restContext, simong.user.id, simongUpdate, () => { + // Create the link, then share it with mrvisser. We will ensure that mrvisser gets the email about the share + RestAPI.Discussions.createDiscussion( + simong.restContext, + 'A talk', + 'about the moon', + 'public', + [], + [], + (err, discussion) => { + assert.ok(!err); - // Collect the createLink activity - EmailTestsUtil.collectAndFetchAllEmails(emails => { - RestAPI.Discussions.shareDiscussion( - simong.restContext, - discussion.id, - [mrvisser.user.id], - err => { - assert.ok(!err); + // Collect the createLink activity + EmailTestsUtil.collectAndFetchAllEmails(emails => { + RestAPI.Discussions.shareDiscussion(simong.restContext, discussion.id, [mrvisser.user.id], err => { + assert.ok(!err); - // Mrvisser should get an email, with simong's information scrubbed - EmailTestsUtil.collectAndFetchAllEmails(emails => { - // There should be exactly one email, the one sent to mrvisser - assert.strictEqual(emails.length, 1); - - const stringEmail = JSON.stringify(emails[0]); - const email = emails[0]; - - // Sanity check that the email is to mrvisser - assert.strictEqual(email.to[0].address, mrvisser.user.email); - - // Ensure some data expected to be in the email is there - assert.notStrictEqual( - stringEmail.indexOf(simong.restContext.hostHeader), - -1 - ); - assert.notStrictEqual(stringEmail.indexOf(discussion.profilePath), -1); - assert.notStrictEqual(stringEmail.indexOf(discussion.displayName), -1); - - // Ensure simong's private info is nowhere to be found - assert.strictEqual(stringEmail.indexOf(simong.user.displayName), -1); - assert.strictEqual(stringEmail.indexOf(simong.user.email), -1); - assert.strictEqual(stringEmail.indexOf(simong.user.locale), -1); - - // The email should contain the public alias - assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); - return callback(); - }); - } - ); + // Mrvisser should get an email, with simong's information scrubbed + EmailTestsUtil.collectAndFetchAllEmails(emails => { + // There should be exactly one email, the one sent to mrvisser + assert.strictEqual(emails.length, 1); + + const stringEmail = JSON.stringify(emails[0]); + const email = emails[0]; + + // Sanity check that the email is to mrvisser + assert.strictEqual(email.to[0].address, mrvisser.user.email); + + // Ensure some data expected to be in the email is there + assert.notStrictEqual(stringEmail.indexOf(simong.restContext.hostHeader), -1); + assert.notStrictEqual(stringEmail.indexOf(discussion.profilePath), -1); + assert.notStrictEqual(stringEmail.indexOf(discussion.displayName), -1); + + // Ensure simong's private info is nowhere to be found + assert.strictEqual(stringEmail.indexOf(simong.user.displayName), -1); + assert.strictEqual(stringEmail.indexOf(simong.user.email), -1); + assert.strictEqual(stringEmail.indexOf(simong.user.locale), -1); + + // The email should contain the public alias + assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); + return callback(); + }); }); - } - ); - } - ); + }); + } + ); + }); }); }); }); diff --git a/packages/oae-discussions/tests/test-discussions.js b/packages/oae-discussions/tests/test-discussions.js index 8766f4bf52..322ce7beaf 100644 --- a/packages/oae-discussions/tests/test-discussions.js +++ b/packages/oae-discussions/tests/test-discussions.js @@ -14,22 +14,17 @@ */ /* eslint-disable no-unused-vars */ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const _ = require('underscore'); - -const AuthzAPI = require('oae-authz'); -const ConfigTestsUtil = require('oae-config/lib/test/util'); -const LibraryAPI = require('oae-library'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const RestUtil = require('oae-rest/lib/util'); -const TestsUtil = require('oae-tests'); - -const DiscussionsConfig = require('oae-config').config('oae-discussions'); -const DiscussionsDAO = require('oae-discussions/lib/internal/dao'); -const DiscussionsTestsUtil = require('oae-discussions/lib/test/util'); +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import _ from 'underscore'; + +import * as ConfigTestsUtil from 'oae-config/lib/test/util'; +import * as LibraryAPI from 'oae-library'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as DiscussionsDAO from 'oae-discussions/lib/internal/dao'; +import * as DiscussionsTestsUtil from 'oae-discussions/lib/test/util'; describe('Discussions', () => { let camAnonymousRestCtx = null; diff --git a/packages/oae-discussions/tests/test-library-search.js b/packages/oae-discussions/tests/test-library-search.js index 59de725b3e..d3a0380524 100644 --- a/packages/oae-discussions/tests/test-library-search.js +++ b/packages/oae-discussions/tests/test-library-search.js @@ -14,12 +14,12 @@ */ /* esling-disable no-unused-vars */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const RestAPI = require('oae-rest'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); +import * as RestAPI from 'oae-rest'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests'; describe('Discussion Library Search', () => { // REST contexts we can use to do REST requests diff --git a/packages/oae-discussions/tests/test-library.js b/packages/oae-discussions/tests/test-library.js index 32cf0c4f6b..0985e1275f 100644 --- a/packages/oae-discussions/tests/test-library.js +++ b/packages/oae-discussions/tests/test-library.js @@ -14,15 +14,14 @@ */ /* esling-disable no-unused-vars */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const LibraryAPI = require('oae-library'); -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); - -const DiscussionsDAO = require('oae-discussions/lib/internal/dao'); -const DiscussionsTestUtil = require('oae-discussions/lib/test/util'); +import * as LibraryAPI from 'oae-library'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as DiscussionsDAO from 'oae-discussions/lib/internal/dao'; +import * as DiscussionsTestUtil from 'oae-discussions/lib/test/util'; describe('Discussion libraries', () => { /** @@ -52,6 +51,7 @@ describe('Discussion libraries', () => { assert.strictEqual(err.code, 401); assert.ok(!items); } + callback(); }); }; @@ -71,113 +71,99 @@ describe('Discussion libraries', () => { // Create a user with the proper visibility TestsUtil.generateTestUsers(restCtx, 1, (err, users) => { const user = _.values(users)[0]; - RestAPI.User.updateUser( - user.restContext, - user.user.id, - { visibility: userVisibility }, - err => { - assert.ok(!err); - - // Fill up this user his library with 3 discussion items. - RestAPI.Discussions.createDiscussion( - user.restContext, - 'name', - 'description', - 'private', - null, - null, - (err, privateDiscussion) => { - assert.ok(!err); - RestAPI.Discussions.createDiscussion( - user.restContext, - 'name', - 'description', - 'loggedin', - null, - null, - (err, loggedinDiscussion) => { - assert.ok(!err); - RestAPI.Discussions.createDiscussion( - user.restContext, - 'name', - 'description', - 'public', - null, - null, - (err, publicDiscussion) => { - assert.ok(!err); - callback(user, privateDiscussion, loggedinDiscussion, publicDiscussion); - } - ); - } - ); - } - ); - } - ); - }); - }; - - /** - * Creates a group with the supplied visibility and fill its library with 3 discussions. - * - * @param {RestContext} restCtx The context with which to create the group and discusion - * @param {String} groupVisibility The visibility for the new group - * @param {Function} callback Standard callback function - * @param {Group} callback.group The created group - * @param {Discussion} callback.privateDiscussion The private discussion - * @param {Discussion} callback.loggedinDiscussion The loggedin discussion - * @param {Discussion} callback.publicDiscussion The public discussion - */ - const createGroupAndLibrary = function(restCtx, groupVisibility, callback) { - RestAPI.Group.createGroup( - restCtx, - 'displayName', - 'description', - groupVisibility, - 'no', - [], - [], - (err, group) => { + RestAPI.User.updateUser(user.restContext, user.user.id, { visibility: userVisibility }, err => { assert.ok(!err); - // Fill up the group library with 3 discussion items. + // Fill up this user his library with 3 discussion items. RestAPI.Discussions.createDiscussion( - restCtx, + user.restContext, 'name', 'description', 'private', - [group.id], + null, null, (err, privateDiscussion) => { assert.ok(!err); RestAPI.Discussions.createDiscussion( - restCtx, + user.restContext, 'name', 'description', 'loggedin', - [group.id], + null, null, (err, loggedinDiscussion) => { assert.ok(!err); RestAPI.Discussions.createDiscussion( - restCtx, + user.restContext, 'name', 'description', 'public', - [group.id], + null, null, (err, publicDiscussion) => { assert.ok(!err); - callback(group, privateDiscussion, loggedinDiscussion, publicDiscussion); + callback(user, privateDiscussion, loggedinDiscussion, publicDiscussion); } ); } ); } ); - } - ); + }); + }); + }; + + /** + * Creates a group with the supplied visibility and fill its library with 3 discussions. + * + * @param {RestContext} restCtx The context with which to create the group and discusion + * @param {String} groupVisibility The visibility for the new group + * @param {Function} callback Standard callback function + * @param {Group} callback.group The created group + * @param {Discussion} callback.privateDiscussion The private discussion + * @param {Discussion} callback.loggedinDiscussion The loggedin discussion + * @param {Discussion} callback.publicDiscussion The public discussion + */ + const createGroupAndLibrary = function(restCtx, groupVisibility, callback) { + RestAPI.Group.createGroup(restCtx, 'displayName', 'description', groupVisibility, 'no', [], [], (err, group) => { + assert.ok(!err); + + // Fill up the group library with 3 discussion items. + RestAPI.Discussions.createDiscussion( + restCtx, + 'name', + 'description', + 'private', + [group.id], + null, + (err, privateDiscussion) => { + assert.ok(!err); + RestAPI.Discussions.createDiscussion( + restCtx, + 'name', + 'description', + 'loggedin', + [group.id], + null, + (err, loggedinDiscussion) => { + assert.ok(!err); + RestAPI.Discussions.createDiscussion( + restCtx, + 'name', + 'description', + 'public', + [group.id], + null, + (err, publicDiscussion) => { + assert.ok(!err); + callback(group, privateDiscussion, loggedinDiscussion, publicDiscussion); + } + ); + } + ); + } + ); + }); }; let camAnonymousRestCtx = null; @@ -201,12 +187,7 @@ describe('Discussion libraries', () => { createUserAndLibrary( camAdminRestCtx, 'private', - ( - privateUser, - privateUserPrivateDiscussion, - privateUserLoggedinDiscussion, - privateUserPublicDiscussion - ) => { + (privateUser, privateUserPrivateDiscussion, privateUserLoggedinDiscussion, privateUserPublicDiscussion) => { createUserAndLibrary( camAdminRestCtx, 'loggedin', @@ -219,42 +200,25 @@ describe('Discussion libraries', () => { createUserAndLibrary( camAdminRestCtx, 'public', - ( - publicUser, - publicUserPrivateDiscussion, - publicUserLoggedinDiscussion, - publicUserPublicDiscussion - ) => { + (publicUser, publicUserPrivateDiscussion, publicUserLoggedinDiscussion, publicUserPublicDiscussion) => { // Each user should be able to see all the items in his library. checkLibrary( privateUser.restContext, privateUser.user.id, true, - [ - privateUserPublicDiscussion, - privateUserLoggedinDiscussion, - privateUserPrivateDiscussion - ], + [privateUserPublicDiscussion, privateUserLoggedinDiscussion, privateUserPrivateDiscussion], () => { checkLibrary( loggedinUser.restContext, loggedinUser.user.id, true, - [ - loggedinUserPublicDiscussion, - loggedinUserLoggedinDiscussion, - loggedinUserPrivateDiscussion - ], + [loggedinUserPublicDiscussion, loggedinUserLoggedinDiscussion, loggedinUserPrivateDiscussion], () => { checkLibrary( publicUser.restContext, publicUser.user.id, true, - [ - publicUserPublicDiscussion, - publicUserLoggedinDiscussion, - publicUserPrivateDiscussion - ], + [publicUserPublicDiscussion, publicUserLoggedinDiscussion, publicUserPrivateDiscussion], () => { // The anonymous user can only see the public stream of the public user. checkLibrary( @@ -263,195 +227,143 @@ describe('Discussion libraries', () => { true, [publicUserPublicDiscussion], () => { - checkLibrary( - camAnonymousRestCtx, - loggedinUser.user.id, - false, - [], - () => { + checkLibrary(camAnonymousRestCtx, loggedinUser.user.id, false, [], () => { + checkLibrary(camAnonymousRestCtx, privateUser.user.id, false, [], () => { checkLibrary( - camAnonymousRestCtx, - privateUser.user.id, - false, - [], + gtAnonymousRestCtx, + publicUser.user.id, + true, + [publicUserPublicDiscussion], () => { - checkLibrary( - gtAnonymousRestCtx, - publicUser.user.id, - true, - [publicUserPublicDiscussion], - () => { - checkLibrary( - gtAnonymousRestCtx, - loggedinUser.user.id, - false, - [], - () => { - checkLibrary( - gtAnonymousRestCtx, - privateUser.user.id, - false, - [], - () => { - // A loggedin user on the same tenant can see the loggedin stream for the public and loggedin user. - TestsUtil.generateTestUsers( - camAdminRestCtx, - 1, - (err, users) => { - const anotherUser = _.values(users)[0]; - checkLibrary( - anotherUser.restContext, - publicUser.user.id, - true, - [ - publicUserPublicDiscussion, - publicUserLoggedinDiscussion - ], - () => { - checkLibrary( - anotherUser.restContext, - loggedinUser.user.id, - true, - [ - loggedinUserPublicDiscussion, - loggedinUserLoggedinDiscussion - ], - () => { - checkLibrary( - anotherUser.restContext, - privateUser.user.id, - false, - [], - () => { - // A loggedin user on *another* tenant can only see the public stream for the public user. - TestsUtil.generateTestUsers( - gtAdminRestCtx, - 1, - (err, users) => { - const otherTenantUser = _.values( - users - )[0]; - checkLibrary( - otherTenantUser.restContext, - publicUser.user.id, - true, - [ - publicUserPublicDiscussion - ], - () => { - checkLibrary( - otherTenantUser.restContext, - loggedinUser.user.id, - false, - [], - () => { - checkLibrary( - otherTenantUser.restContext, - privateUser.user - .id, - false, - [], - () => { - // The cambridge tenant admin can see all the things. - checkLibrary( - camAdminRestCtx, - publicUser - .user.id, - true, - [ - publicUserPublicDiscussion, - publicUserLoggedinDiscussion, - publicUserPrivateDiscussion - ], - () => { - checkLibrary( - camAdminRestCtx, - loggedinUser - .user - .id, - true, - [ - loggedinUserPublicDiscussion, - loggedinUserLoggedinDiscussion, - loggedinUserPrivateDiscussion - ], - () => { - checkLibrary( - camAdminRestCtx, - privateUser - .user - .id, - true, - [ - privateUserPublicDiscussion, - privateUserLoggedinDiscussion, - privateUserPrivateDiscussion - ], - () => { - // The GT tenant admin can only see the public stream for the public user. - checkLibrary( - gtAdminRestCtx, - publicUser - .user - .id, - true, - [ - publicUserPublicDiscussion - ], - () => { - checkLibrary( - gtAdminRestCtx, - loggedinUser - .user - .id, - false, - [], - () => { - checkLibrary( - gtAdminRestCtx, - privateUser - .user - .id, - false, - [], - callback - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + checkLibrary(gtAnonymousRestCtx, loggedinUser.user.id, false, [], () => { + checkLibrary(gtAnonymousRestCtx, privateUser.user.id, false, [], () => { + // A loggedin user on the same tenant can see the loggedin stream for the public and loggedin user. + TestsUtil.generateTestUsers(camAdminRestCtx, 1, (err, users) => { + const anotherUser = _.values(users)[0]; + checkLibrary( + anotherUser.restContext, + publicUser.user.id, + true, + [publicUserPublicDiscussion, publicUserLoggedinDiscussion], + () => { + checkLibrary( + anotherUser.restContext, + loggedinUser.user.id, + true, + [loggedinUserPublicDiscussion, loggedinUserLoggedinDiscussion], + () => { + checkLibrary( + anotherUser.restContext, + privateUser.user.id, + false, + [], + () => { + // A loggedin user on *another* tenant can only see the public stream for the public user. + TestsUtil.generateTestUsers( + gtAdminRestCtx, + 1, + (err, users) => { + const otherTenantUser = _.values(users)[0]; + checkLibrary( + otherTenantUser.restContext, + publicUser.user.id, + true, + [publicUserPublicDiscussion], + () => { + checkLibrary( + otherTenantUser.restContext, + loggedinUser.user.id, + false, + [], + () => { + checkLibrary( + otherTenantUser.restContext, + privateUser.user.id, + false, + [], + () => { + // The cambridge tenant admin can see all the things. + checkLibrary( + camAdminRestCtx, + publicUser.user.id, + true, + [ + publicUserPublicDiscussion, + publicUserLoggedinDiscussion, + publicUserPrivateDiscussion + ], + () => { + checkLibrary( + camAdminRestCtx, + loggedinUser.user.id, + true, + [ + loggedinUserPublicDiscussion, + loggedinUserLoggedinDiscussion, + loggedinUserPrivateDiscussion + ], + () => { + checkLibrary( + camAdminRestCtx, + privateUser.user.id, + true, + [ + privateUserPublicDiscussion, + privateUserLoggedinDiscussion, + privateUserPrivateDiscussion + ], + () => { + // The GT tenant admin can only see the public stream for the public user. + checkLibrary( + gtAdminRestCtx, + publicUser.user.id, + true, + [publicUserPublicDiscussion], + () => { + checkLibrary( + gtAdminRestCtx, + loggedinUser.user.id, + false, + [], + () => { + checkLibrary( + gtAdminRestCtx, + privateUser.user.id, + false, + [], + callback + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); + }); + }); } ); - } - ); + }); + }); } ); } @@ -480,12 +392,7 @@ describe('Discussion libraries', () => { createGroupAndLibrary( groupCreator.restContext, 'private', - ( - privateGroup, - privateGroupPrivateDiscussion, - privateGroupLoggedinDiscussion, - privateGroupPublicDiscussion - ) => { + (privateGroup, privateGroupPrivateDiscussion, privateGroupLoggedinDiscussion, privateGroupPublicDiscussion) => { createGroupAndLibrary( groupCreator.restContext, 'loggedin', @@ -505,135 +412,128 @@ describe('Discussion libraries', () => { publicGroupPublicDiscussion ) => { // An anonymous user can only see the public stream for the public group. - checkLibrary( - camAnonymousRestCtx, - publicGroup.id, - true, - [publicGroupPublicDiscussion], - () => { - checkLibrary(camAnonymousRestCtx, loggedinGroup.id, false, [], () => { - checkLibrary(camAnonymousRestCtx, privateGroup.id, false, [], () => { - checkLibrary( - gtAnonymousRestCtx, - publicGroup.id, - true, - [publicGroupPublicDiscussion], - () => { - checkLibrary(gtAnonymousRestCtx, loggedinGroup.id, false, [], () => { - checkLibrary(gtAnonymousRestCtx, privateGroup.id, false, [], () => { - // A loggedin user on the same tenant can see the loggedin stream for the public and loggedin group. + checkLibrary(camAnonymousRestCtx, publicGroup.id, true, [publicGroupPublicDiscussion], () => { + checkLibrary(camAnonymousRestCtx, loggedinGroup.id, false, [], () => { + checkLibrary(camAnonymousRestCtx, privateGroup.id, false, [], () => { + checkLibrary(gtAnonymousRestCtx, publicGroup.id, true, [publicGroupPublicDiscussion], () => { + checkLibrary(gtAnonymousRestCtx, loggedinGroup.id, false, [], () => { + checkLibrary(gtAnonymousRestCtx, privateGroup.id, false, [], () => { + // A loggedin user on the same tenant can see the loggedin stream for the public and loggedin group. + checkLibrary( + anotherUser.restContext, + publicGroup.id, + true, + [publicGroupPublicDiscussion, publicGroupLoggedinDiscussion], + () => { checkLibrary( anotherUser.restContext, - publicGroup.id, + loggedinGroup.id, true, - [publicGroupPublicDiscussion, publicGroupLoggedinDiscussion], + [loggedinGroupPublicDiscussion, loggedinGroupLoggedinDiscussion], () => { - checkLibrary( - anotherUser.restContext, - loggedinGroup.id, - true, - [ - loggedinGroupPublicDiscussion, - loggedinGroupLoggedinDiscussion - ], - () => { + checkLibrary(anotherUser.restContext, privateGroup.id, false, [], () => { + // A loggedin user on *another* tenant can only see the public stream for the public user. + TestsUtil.generateTestUsers(gtAdminRestCtx, 1, (err, users) => { + const otherTenantUser = _.values(users)[0]; checkLibrary( - anotherUser.restContext, - privateGroup.id, - false, - [], + otherTenantUser.restContext, + publicGroup.id, + true, + [publicGroupPublicDiscussion], () => { - // A loggedin user on *another* tenant can only see the public stream for the public user. - TestsUtil.generateTestUsers( - gtAdminRestCtx, - 1, - (err, users) => { - const otherTenantUser = _.values(users)[0]; + checkLibrary( + otherTenantUser.restContext, + loggedinGroup.id, + false, + [], + () => { checkLibrary( otherTenantUser.restContext, - publicGroup.id, - true, - [publicGroupPublicDiscussion], + privateGroup.id, + false, + [], () => { + // The cambridge tenant admin can see all the things. checkLibrary( - otherTenantUser.restContext, - loggedinGroup.id, - false, - [], + camAdminRestCtx, + publicGroup.id, + true, + [ + publicGroupPublicDiscussion, + publicGroupLoggedinDiscussion, + publicGroupPrivateDiscussion + ], () => { checkLibrary( - otherTenantUser.restContext, - privateGroup.id, - false, - [], + camAdminRestCtx, + loggedinGroup.id, + true, + [ + loggedinGroupPublicDiscussion, + loggedinGroupLoggedinDiscussion, + loggedinGroupPrivateDiscussion + ], () => { - // The cambridge tenant admin can see all the things. checkLibrary( camAdminRestCtx, - publicGroup.id, + privateGroup.id, true, [ - publicGroupPublicDiscussion, - publicGroupLoggedinDiscussion, - publicGroupPrivateDiscussion + privateGroupPrivateDiscussion, + privateGroupLoggedinDiscussion, + privateGroupPrivateDiscussion ], () => { + // The GT tenant admin can only see the public stream for the public user. checkLibrary( - camAdminRestCtx, - loggedinGroup.id, + gtAdminRestCtx, + publicGroup.id, true, - [ - loggedinGroupPublicDiscussion, - loggedinGroupLoggedinDiscussion, - loggedinGroupPrivateDiscussion - ], + [publicGroupPublicDiscussion], () => { checkLibrary( - camAdminRestCtx, - privateGroup.id, - true, - [ - privateGroupPrivateDiscussion, - privateGroupLoggedinDiscussion, - privateGroupPrivateDiscussion - ], + gtAdminRestCtx, + loggedinGroup.id, + false, + [], () => { - // The GT tenant admin can only see the public stream for the public user. checkLibrary( gtAdminRestCtx, - publicGroup.id, - true, - [ - publicGroupPublicDiscussion - ], + privateGroup.id, + false, + [], () => { - checkLibrary( - gtAdminRestCtx, - loggedinGroup.id, - false, - [], - () => { + // If we make the cambridge user a member of the private group he should see everything. + let changes = {}; + changes[anotherUser.user.id] = 'member'; + RestAPI.Group.setGroupMembers( + groupCreator.restContext, + privateGroup.id, + changes, + err => { + assert.ok(!err); checkLibrary( - gtAdminRestCtx, + anotherUser.restContext, privateGroup.id, - false, - [], + true, + [ + privateGroupPrivateDiscussion, + privateGroupLoggedinDiscussion, + privateGroupPrivateDiscussion + ], () => { - // If we make the cambridge user a member of the private group he should see everything. - let changes = {}; - changes[ - anotherUser.user.id - ] = 'member'; + // If we make the GT user a member of the private group, he should see everything. + changes = {}; + changes[otherTenantUser.user.id] = + 'member'; RestAPI.Group.setGroupMembers( groupCreator.restContext, privateGroup.id, changes, err => { - assert.ok( - !err - ); + assert.ok(!err); checkLibrary( - anotherUser.restContext, + otherTenantUser.restContext, privateGroup.id, true, [ @@ -641,35 +541,7 @@ describe('Discussion libraries', () => { privateGroupLoggedinDiscussion, privateGroupPrivateDiscussion ], - () => { - // If we make the GT user a member of the private group, he should see everything. - changes = {}; - changes[ - otherTenantUser.user.id - ] = - 'member'; - RestAPI.Group.setGroupMembers( - groupCreator.restContext, - privateGroup.id, - changes, - err => { - assert.ok( - !err - ); - checkLibrary( - otherTenantUser.restContext, - privateGroup.id, - true, - [ - privateGroupPrivateDiscussion, - privateGroupLoggedinDiscussion, - privateGroupPrivateDiscussion - ], - callback - ); - } - ); - } + callback ); } ); @@ -695,18 +567,18 @@ describe('Discussion libraries', () => { ); } ); - } - ); + }); + }); } ); - }); - }); - } - ); + } + ); + }); + }); }); }); - } - ); + }); + }); } ); } diff --git a/packages/oae-discussions/tests/test-push.js b/packages/oae-discussions/tests/test-push.js index 1acb3e8e3b..6e2221c572 100644 --- a/packages/oae-discussions/tests/test-push.js +++ b/packages/oae-discussions/tests/test-push.js @@ -14,17 +14,15 @@ */ /* esling-disable no-unused-vars */ -const assert = require('assert'); -const fs = require('fs'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityTestsUtil = require('oae-activity/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const TestsUtil = require('oae-tests'); +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as ActivityTestsUtil from 'oae-activity/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; -const { DiscussionsConstants } = require('oae-discussions/lib/constants'); +import { DiscussionsConstants } from 'oae-discussions/lib/constants'; describe('Discussion Push', () => { // Rest contexts that can be used performing rest requests @@ -34,9 +32,7 @@ describe('Discussion Push', () => { * Function that will fill up the tenant admin and anymous rest contexts */ before(callback => { - localAdminRestContext = TestsUtil.createTenantAdminRestContext( - global.oaeTests.tenants.localhost.host - ); + localAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.localhost.host); callback(); }); @@ -71,84 +67,80 @@ describe('Discussion Push', () => { null, (err, discussion) => { assert.ok(!err); - RestAPI.Discussions.getDiscussion( - simong.restContext, - discussion.id, - (err, discussion) => { - assert.ok(!err); - - // Ensure we get a 400 error with an invalid activity stream id - client.subscribe(discussion.id, null, discussion.signature, null, err => { + RestAPI.Discussions.getDiscussion(simong.restContext, discussion.id, (err, discussion) => { + assert.ok(!err); + + // Ensure we get a 400 error with an invalid activity stream id + client.subscribe(discussion.id, null, discussion.signature, null, err => { + assert.strictEqual(err.code, 400); + + // Ensure we get a 400 error with a missing resource id + client.subscribe(null, 'activity', discussion.signature, null, err => { assert.strictEqual(err.code, 400); - // Ensure we get a 400 error with a missing resource id - client.subscribe(null, 'activity', discussion.signature, null, err => { - assert.strictEqual(err.code, 400); - - // Ensure we get a 400 error with an invalid token - client.subscribe( - discussion.id, - 'activity', - { signature: discussion.signature.signature }, - null, - err => { - assert.strictEqual(err.code, 401); - client.subscribe( - discussion.id, - 'activity', - { expires: discussion.signature.expires }, - null, - err => { - assert.strictEqual(err.code, 401); - - // Ensure we get a 401 error with an incorrect signature - client.subscribe( - discussion.id, - 'activity', - { expires: Date.now() + 10000, signature: 'foo' }, - null, - err => { - assert.strictEqual(err.code, 401); - - // Simon should not be able to use a signature that was generated for Branden - RestAPI.Discussions.getDiscussion( - branden.restContext, - discussion.id, - (err, discussionForBranden) => { - assert.ok(!err); - client.subscribe( - discussion.id, - 'activity', - discussionForBranden.signature, - null, - err => { - assert.strictEqual(err.code, 401); - - // Sanity check that a valid signature works - client.subscribe( - discussion.id, - 'activity', - discussion.signature, - null, - err => { - assert.ok(!err); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - }); + // Ensure we get a 400 error with an invalid token + client.subscribe( + discussion.id, + 'activity', + { signature: discussion.signature.signature }, + null, + err => { + assert.strictEqual(err.code, 401); + client.subscribe( + discussion.id, + 'activity', + { expires: discussion.signature.expires }, + null, + err => { + assert.strictEqual(err.code, 401); + + // Ensure we get a 401 error with an incorrect signature + client.subscribe( + discussion.id, + 'activity', + { expires: Date.now() + 10000, signature: 'foo' }, + null, + err => { + assert.strictEqual(err.code, 401); + + // Simon should not be able to use a signature that was generated for Branden + RestAPI.Discussions.getDiscussion( + branden.restContext, + discussion.id, + (err, discussionForBranden) => { + assert.ok(!err); + client.subscribe( + discussion.id, + 'activity', + discussionForBranden.signature, + null, + err => { + assert.strictEqual(err.code, 401); + + // Sanity check that a valid signature works + client.subscribe( + discussion.id, + 'activity', + discussion.signature, + null, + err => { + assert.ok(!err); + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); }); - } - ); + }); + }); } ); }); @@ -191,46 +183,37 @@ describe('Discussion Push', () => { [], (err, discussion) => { assert.ok(!err); - RestAPI.Discussions.getDiscussion( - contexts.simon.restContext, - discussion.id, - (err, discussion) => { - assert.ok(!err); + RestAPI.Discussions.getDiscussion(contexts.simon.restContext, discussion.id, (err, discussion) => { + assert.ok(!err); - // Route and deliver activities - ActivityTestsUtil.collectAndGetActivityStream( - contexts.simon.restContext, - null, - null, - () => { - // Register for some streams - const data = { - authentication: { - userId: contexts.simon.user.id, - tenantAlias: simonFull.tenant.alias, - signature: simonFull.signature - }, - streams: [ - { - resourceId: discussion.id, - streamType: 'activity', - token: discussion.signature - }, - { - resourceId: discussion.id, - streamType: 'message', - token: discussion.signature - } - ] - }; - - ActivityTestsUtil.getFullySetupPushClient(data, client => { - callback(contexts, discussion, client); - }); - } - ); - } - ); + // Route and deliver activities + ActivityTestsUtil.collectAndGetActivityStream(contexts.simon.restContext, null, null, () => { + // Register for some streams + const data = { + authentication: { + userId: contexts.simon.user.id, + tenantAlias: simonFull.tenant.alias, + signature: simonFull.signature + }, + streams: [ + { + resourceId: discussion.id, + streamType: 'activity', + token: discussion.signature + }, + { + resourceId: discussion.id, + streamType: 'message', + token: discussion.signature + } + ] + }; + + ActivityTestsUtil.getFullySetupPushClient(data, client => { + callback(contexts, discussion, client); + }); + }); + }); } ); }); diff --git a/packages/oae-discussions/tests/test-search.js b/packages/oae-discussions/tests/test-search.js index 5a58583495..92d420b534 100644 --- a/packages/oae-discussions/tests/test-search.js +++ b/packages/oae-discussions/tests/test-search.js @@ -14,13 +14,13 @@ */ /* esling-disable no-unused-vars */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const RestAPI = require('oae-rest'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as RestAPI from 'oae-rest'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests'; describe('Discussion Search', () => { // REST contexts we can use to do REST requests diff --git a/packages/oae-doc/lib/api.js b/packages/oae-doc/lib/api.js index 02c1f03c38..93e874318b 100644 --- a/packages/oae-doc/lib/api.js +++ b/packages/oae-doc/lib/api.js @@ -13,17 +13,16 @@ * permissions and limitations under the License. */ -const fs = require('fs'); +import { stat as doesFileExist, readFile } from 'fs'; +import _ from 'underscore'; +import dox from 'dox'; -const _ = require('underscore'); -const dox = require('dox'); -const { logger } = require('oae-logger'); +import { getFileListForFolder } from 'oae-util/lib/io'; +import * as modules from 'oae-util/lib/modules'; +import * as OaeUtil from 'oae-util/lib/util'; +import { Validator } from 'oae-util/lib/validator'; -const IO = require('oae-util/lib/io'); -const modules = require('oae-util/lib/modules'); -const OaeUtil = require('oae-util/lib/util'); - -const { Validator } = require('oae-util/lib/validator'); +import { logger } from 'oae-logger'; const log = logger('oae-doc'); @@ -69,7 +68,7 @@ const _initializeFrontendDocs = function(uiConfig, callback) { // for generating documentation. If the `original` folder does not exist, we assume that we are not running on an // optimized build and use the source files in the provided base UI directory. const originalDir = baseDir + '/../original'; - fs.stat(originalDir, (err, exists) => { + doesFileExist(originalDir, (err, exists) => { baseDir = exists ? originalDir : baseDir; // Only parse the API files. We don't parse any other UI files yet. @@ -129,7 +128,7 @@ const _initializeBackendDocs = function(modules, callback) { */ const _parseDocs = function(dir, exclude, callback) { // Get all of the files in the provided base directory - IO.getFileListForFolder(dir, (err, fileNames) => { + getFileListForFolder(dir, (err, fileNames) => { if (err) { log().warn({ err, dir }, 'Failed getting file list to parse dox documentation.'); return callback({ code: 404, msg: 'No documentation for this module was found' }); @@ -144,7 +143,7 @@ const _parseDocs = function(dir, exclude, callback) { _.each(fileNames, fileName => { (function(fileName) { // Read each of the files in the provided directory - fs.readFile(dir + '/' + fileName, 'utf8', (err, data) => { + readFile(dir + '/' + fileName, 'utf8', (err, data) => { done++; if (err) { log().error({ err }, 'Failed reading ' + dir + '/' + fileName); @@ -246,4 +245,4 @@ const getModuleDocumentation = function(moduleId, type, callback) { return callback({ code: 404, msg: 'No documentation for this module was found' }); }; -module.exports = { getModules, initializeDocs, getModuleDocumentation }; +export { getModules, initializeDocs, getModuleDocumentation }; diff --git a/packages/oae-doc/lib/init.js b/packages/oae-doc/lib/init.js index fead132922..4434a2bf47 100644 --- a/packages/oae-doc/lib/init.js +++ b/packages/oae-doc/lib/init.js @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -const DocAPI = require('./api'); +import { initializeDocs } from './api'; -module.exports = function(config, callback) { - DocAPI.initializeDocs(config.ui, callback); +export const init = function(config, callback) { + initializeDocs(config.ui, callback); }; diff --git a/packages/oae-doc/lib/rest.js b/packages/oae-doc/lib/rest.js index 040fd2ab8d..59098c755d 100644 --- a/packages/oae-doc/lib/rest.js +++ b/packages/oae-doc/lib/rest.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const OAE = require('oae-util/lib/oae'); -const Swagger = require('oae-util/lib/swagger'); -const DocAPI = require('./api'); +import * as OAE from 'oae-util/lib/oae'; +import * as Swagger from 'oae-util/lib/swagger'; +import { getModuleDocumentation, getModules } from './api'; /** * @REST getDocType @@ -31,7 +31,7 @@ const DocAPI = require('./api'); * @HttpResponse 400 Invalid or missing module type. Accepted values are "backend" and "frontend" */ const _getDocModulesByType = function(req, res) { - DocAPI.getModules(req.params.type, function(err, modules) { + getModules(req.params.type, function(err, modules) { if (err) { return res.status(err.code).send(err.msg); } @@ -60,7 +60,7 @@ OAE.globalAdminRouter.on('get', '/api/doc/:type', _getDocModulesByType); * @HttpResponse 404 No documentation for this module was found */ const _getDocModule = function(req, res) { - DocAPI.getModuleDocumentation(req.params.module, req.params.type, function(err, docs) { + getModuleDocumentation(req.params.module, req.params.type, function(err, docs) { if (err) { return res.status(err.code).send(err.msg); } diff --git a/packages/oae-doc/tests/test-doc.js b/packages/oae-doc/tests/test-doc.js index c4293eb74f..28f0a1d5f3 100644 --- a/packages/oae-doc/tests/test-doc.js +++ b/packages/oae-doc/tests/test-doc.js @@ -13,11 +13,11 @@ * visibilitys and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; describe('Docs', () => { // Rest context that can be used every time we need to make a request as an anonymous user @@ -26,7 +26,7 @@ describe('Docs', () => { /** * Function that will fill up the global admin, tenant admin and anymous rest context */ - before((callback) => { + before(callback => { // Fill up the anonymous cam rest context anonymousCamRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); callback(); @@ -36,7 +36,7 @@ describe('Docs', () => { /** * Test that verifies that it is possible to get a list of all of the available modules */ - it('verify get modules', (callback) => { + it('verify get modules', callback => { // Get the back-end modules RestAPI.Doc.getModules(anonymousCamRestContext, 'backend', (err, backendModules) => { assert.ok(!err); @@ -58,11 +58,8 @@ describe('Docs', () => { /** * Test that verifies that validation is done appropriately */ - it('verify validation', (callback) => { - RestAPI.Doc.getModules(anonymousCamRestContext, 'invalid module type', ( - err, - modules - ) => { + it('verify validation', callback => { + RestAPI.Doc.getModules(anonymousCamRestContext, 'invalid module type', (err, modules) => { assert.strictEqual(err.code, 400); assert.ok(!modules); @@ -75,90 +72,62 @@ describe('Docs', () => { /** * Test that verifies that the JSDocs for an existing module can be retrieved */ - it('verify get module documentation', (callback) => { + it('verify get module documentation', callback => { // Get the documentation for a back-end module - RestAPI.Doc.getModuleDocumentation(anonymousCamRestContext, 'backend', 'oae-doc', ( - err, - docs - ) => { + RestAPI.Doc.getModuleDocumentation(anonymousCamRestContext, 'backend', 'oae-doc', (err, docs) => { assert.ok(!err); assert.ok(docs); assert.ok(_.keys(docs).length); assert.ok(_.keys(docs['api.js']).length); // Get the documentation for a front-end module - RestAPI.Doc.getModuleDocumentation( - anonymousCamRestContext, - 'frontend', - 'oae.api.util.js', - (err, docs) => { - assert.ok(!err); - assert.ok(docs); - assert.ok(docs.length); - callback(); - } - ); + RestAPI.Doc.getModuleDocumentation(anonymousCamRestContext, 'frontend', 'oae.api.util.js', (err, docs) => { + assert.ok(!err); + assert.ok(docs); + assert.ok(docs.length); + callback(); + }); }); }); /** * Test that verifies that validation is done appropriately */ - it('verify validation', (callback) => { + it('verify validation', callback => { // Get non-existing back-end module - RestAPI.Doc.getModuleDocumentation( - anonymousCamRestContext, - 'backend', - 'oae-non-existing', - (err, docs) => { + RestAPI.Doc.getModuleDocumentation(anonymousCamRestContext, 'backend', 'oae-non-existing', (err, docs) => { + assert.strictEqual(err.code, 404); + assert.ok(!docs); + // Get non-existing back-end module + RestAPI.Doc.getModuleDocumentation(anonymousCamRestContext, 'backend', 'oae.api.nonexisting', (err, docs) => { assert.strictEqual(err.code, 404); assert.ok(!docs); - // Get non-existing back-end module - RestAPI.Doc.getModuleDocumentation( - anonymousCamRestContext, - 'backend', - 'oae.api.nonexisting', - (err, docs) => { + + // Get non-OAE back-end module + RestAPI.Doc.getModuleDocumentation(anonymousCamRestContext, 'backend', 'helenus', (err, docs) => { + assert.strictEqual(err.code, 404); + assert.ok(!docs); + // Get non-OAE front-end module + RestAPI.Doc.getModuleDocumentation(anonymousCamRestContext, 'frontend', 'test', (err, docs) => { assert.strictEqual(err.code, 404); assert.ok(!docs); - // Get non-OAE back-end module + // Get an invalid module type RestAPI.Doc.getModuleDocumentation( anonymousCamRestContext, - 'backend', - 'helenus', + 'invalid module type', + 'oae.api.util.js', (err, docs) => { - assert.strictEqual(err.code, 404); + assert.strictEqual(err.code, 400); assert.ok(!docs); - // Get non-OAE front-end module - RestAPI.Doc.getModuleDocumentation( - anonymousCamRestContext, - 'frontend', - 'test', - (err, docs) => { - assert.strictEqual(err.code, 404); - assert.ok(!docs); - // Get an invalid module type - RestAPI.Doc.getModuleDocumentation( - anonymousCamRestContext, - 'invalid module type', - 'oae.api.util.js', - (err, docs) => { - assert.strictEqual(err.code, 400); - assert.ok(!docs); - - return callback(); - } - ); - } - ); + return callback(); } ); - } - ); - } - ); + }); + }); + }); + }); }); }); }); diff --git a/packages/oae-email/config/email.js b/packages/oae-email/config/email.js index f0f3949097..0a35d955f6 100644 --- a/packages/oae-email/config/email.js +++ b/packages/oae-email/config/email.js @@ -13,16 +13,20 @@ * permissions and limitations under the License. */ -var Fields = require('oae-config/lib/fields'); +import * as Fields from 'oae-config/lib/fields'; -module.exports = { - 'title': 'OAE Email Module', - 'general': { - 'name': 'General', - 'description': 'General e-mail configuration', - 'elements': { - 'fromName': new Fields.Text('Sender Name', 'The name that will appear in the "From" header for emails sent by the system. e.g., "Apereo OAE"'), - 'fromAddress': new Fields.Text('Sender Address', 'The address that will appear in the "From" header for emails sent by the system. e.g., "noreply@example.com"') - } - } +export const title = 'OAE Email Module'; +export const general = { + name: 'General', + description: 'General e-mail configuration', + elements: { + fromName: new Fields.Text( + 'Sender Name', + 'The name that will appear in the "From" header for emails sent by the system. e.g., "Apereo OAE"' + ), + fromAddress: new Fields.Text( + 'Sender Address', + 'The address that will appear in the "From" header for emails sent by the system. e.g., "noreply@example.com"' + ) + } }; diff --git a/packages/oae-email/lib/api.js b/packages/oae-email/lib/api.js index 51c36cac8e..6b3f56a670 100755 --- a/packages/oae-email/lib/api.js +++ b/packages/oae-email/lib/api.js @@ -13,32 +13,40 @@ * permissions and limitations under the License. */ -const crypto = require('crypto'); -const fs = require('fs'); -const util = require('util'); -const path = require('path'); -const { MailParser } = require('mailparser'); -const redback = require('redback'); -const juice = require('juice'); -const stubTransport = require('nodemailer-stub-transport'); -const sendmailTransport = require('nodemailer-sendmail-transport'); -const _ = require('underscore'); -const nodemailer = require('nodemailer'); -const { htmlToText } = require('nodemailer-html-to-text'); - -const Counter = require('oae-util/lib/counter'); -const EmailConfig = require('oae-config').config('oae-email'); -const EmitterAPI = require('oae-emitter'); -const IO = require('oae-util/lib/io'); -const Locking = require('oae-util/lib/locking'); -const log = require('oae-logger').logger('oae-email'); -const OaeModules = require('oae-util/lib/modules'); -const Redis = require('oae-util/lib/redis'); -const Telemetry = require('oae-telemetry').telemetry('oae-email'); +import crypto from 'crypto'; +import fs from 'fs'; +import util from 'util'; +import path from 'path'; +import redback from 'redback'; +import juice from 'juice'; +import stubTransport from 'nodemailer-stub-transport'; +import sendmailTransport from 'nodemailer-sendmail-transport'; +import _ from 'underscore'; +import nodemailer from 'nodemailer'; +import { logger } from 'oae-logger'; +import { setUpConfig } from 'oae-config'; +import { telemetry } from 'oae-telemetry'; + +import Counter from 'oae-util/lib/counter'; +import * as EmitterAPI from 'oae-emitter'; +import * as IO from 'oae-util/lib/io'; +import * as Locking from 'oae-util/lib/locking'; +import * as OaeModules from 'oae-util/lib/modules'; +import * as Redis from 'oae-util/lib/redis'; + +import * as UIAPI from 'oae-ui'; +import { htmlToText } from 'nodemailer-html-to-text'; +import { MailParser } from 'mailparser'; +import { Validator } from 'oae-util/lib/validator'; + +const EmailConfig = setUpConfig('oae-email'); + +const log = logger('oae-email'); + +const Telemetry = telemetry('oae-email'); const TenantsAPI = require('oae-tenants'); -const TenantsConfig = require('oae-config').config('oae-tenants'); -const UIAPI = require('oae-ui'); -const { Validator } = require('oae-util/lib/validator'); + +const TenantsConfig = setUpConfig('oae-tenants'); let EmailRateLimiter = null; @@ -121,18 +129,18 @@ const init = function(emailSystemConfig, callback) { const EmailRedback = redback.use(Redis.getClient(), { namespace: 'oae-email:redback' }); /*! - * For robust unit tests, any provided timespan needs to cover at least 2 buckets so that when - * we do a count on the rate, we don't risk rolling over to a new interval and miss the emails - * we just sent, resetting the frequency to 0 and intermittently failing the test. Therefore - * we set the bucket interval to be (timespan / 2). - * - * Additionally, when a bucket is incremented in redback, the following 2 buckets are cleared. - * Therefore in order to ensure we don't roll over to a new bucket while incrementing and risking - * our previous bucket getting cleared, we must ensure we have at least 5 buckets so that the - * clearing of the "next 2" buckets does not impact the counting of the "previous 2". (e.g., if - * the current time bucket is 2, redback will clear buckets 3 and 4 while we count back from 0, - * 1 and 2). - */ + * For robust unit tests, any provided timespan needs to cover at least 2 buckets so that when + * we do a count on the rate, we don't risk rolling over to a new interval and miss the emails + * we just sent, resetting the frequency to 0 and intermittently failing the test. Therefore + * we set the bucket interval to be (timespan / 2). + * + * Additionally, when a bucket is incremented in redback, the following 2 buckets are cleared. + * Therefore in order to ensure we don't roll over to a new bucket while incrementing and risking + * our previous bucket getting cleared, we must ensure we have at least 5 buckets so that the + * clearing of the "next 2" buckets does not impact the counting of the "previous 2". (e.g., if + * the current time bucket is 2, redback will clear buckets 3 and 4 while we count back from 0, + * 1 and 2). + */ const bucketInterval = Math.ceil(throttleConfig.timespan / 2); EmailRateLimiter = EmailRedback.createRateLimit('email', { // The rate limiter seems to need at least 5 buckets to work, so lets give it exactly 5 (there are exactly bucket_span / bucket_interval buckets) @@ -157,13 +165,8 @@ const init = function(emailSystemConfig, callback) { log().info({ data: emailSystemConfig.smtpTransport }, 'Configuring SMTP email transport.'); emailTransport = nodemailer.createTransport(emailSystemConfig.smtpTransport); } else if (emailSystemConfig.transport === 'sendmail') { - log().info( - { data: emailSystemConfig.sendmailTransport }, - 'Configuring Sendmail email transport.' - ); - emailTransport = nodemailer.createTransport( - sendmailTransport(emailSystemConfig.sendmailTransport.path) - ); + log().info({ data: emailSystemConfig.sendmailTransport }, 'Configuring Sendmail email transport.'); + emailTransport = nodemailer.createTransport(sendmailTransport(emailSystemConfig.sendmailTransport.path)); } else { log().error( { @@ -240,9 +243,7 @@ const sendEmail = function(templateModule, templateId, recipient, data, opts, ca const validator = new Validator(); validator.check(templateModule, { code: 400, msg: 'Must specify a template module' }).notEmpty(); validator.check(templateId, { code: 400, msg: 'Must specify a template id' }).notEmpty(); - validator - .check(null, { code: 400, msg: 'Must specify a user when sending an email' }) - .isObject(recipient); + validator.check(null, { code: 400, msg: 'Must specify a user when sending an email' }).isObject(recipient); // Only validate the user email if it was a valid object if (recipient) { @@ -291,6 +292,7 @@ const sendEmail = function(templateModule, templateId, recipient, data, opts, ca ); return callback(noMetaTemplateErr); } + if (!htmlTemplate && !txtTemplate) { const noContentTemplateErr = { code: 500, @@ -406,8 +408,7 @@ const sendEmail = function(templateModule, templateId, recipient, data, opts, ca // eslint-disable-next-line no-template-curly-in-string fromName = fromName.replace('${tenant}', tenant.displayName); const fromAddr = - EmailConfig.getValue(tenant.alias, 'general', 'fromAddress') || - util.format('noreply@%s', tenant.host); + EmailConfig.getValue(tenant.alias, 'general', 'fromAddress') || util.format('noreply@%s', tenant.host); const from = util.format('"%s" <%s>', fromName, fromAddr); // Build the email object that will be sent through nodemailer. The 'from' property can be overridden by @@ -437,21 +438,16 @@ const sendEmail = function(templateModule, templateId, recipient, data, opts, ca // a source location of the message. We also add the userid of the user // we sent the message to, so we can determine what user a message was // sent to in Sendgrid - emailInfo.messageId = util.format( - '%s.%s@%s', - opts.hash, - recipient.id.replace(/:/g, '-'), - tenant.host - ); + emailInfo.messageId = util.format('%s.%s@%s', opts.hash, recipient.id.replace(/:/g, '-'), tenant.host); // Increment our debug sent count. We have to do it here because we // optionally enter an asynchronous block below _incr(); /*! - * Wrapper callback that conveniently decrements the email sent count when - * processing has completed - */ + * Wrapper callback that conveniently decrements the email sent count when + * processing has completed + */ const _decrCallback = function(err) { _decr(); callback(err); @@ -520,6 +516,7 @@ const _sendEmail = function(emailInfo, opts, callback) { log().error({ err, emailInfo }, 'Unable to lock email id'); return callback(err); } + if (!token) { Telemetry.incr('lock.fail'); log().error( @@ -535,6 +532,7 @@ const _sendEmail = function(emailInfo, opts, callback) { log().error({ err }, 'Failed to perform email throttle check'); return callback({ code: 500, msg: 'Failed to perform email throttle check' }); } + if (count > throttleConfig.count - 1) { Telemetry.incr('throttled'); log().warn({ to: emailInfo.to }, 'Throttling in effect'); @@ -553,10 +551,7 @@ const _sendEmail = function(emailInfo, opts, callback) { // We got a lock and aren't throttled, send our mail emailTransport.sendMail(emailInfo, (err, info) => { if (err) { - log().error( - { err, to: emailInfo.to, subject: emailInfo.subject }, - 'Error sending email to recipient' - ); + log().error({ err, to: emailInfo.to, subject: emailInfo.subject }, 'Error sending email to recipient'); return callback(err); } @@ -626,6 +621,7 @@ const _getTemplatesForModules = function(basedir, modules, callback, _templates) if (err) { return callback(err); } + _templates[module] = templatesForModule; return _getTemplatesForModules(basedir, modules, callback, _templates); @@ -649,6 +645,7 @@ const _getTemplatesForModule = function(basedir, module, callback) { if (err) { return callback(err); } + if (_.isEmpty(files)) { return callback(); } @@ -755,6 +752,7 @@ const _getCompiledTemplate = function(templatePath, callback) { const compiledTemplate = UIAPI.compileTemplate(templateContent); return callback(null, compiledTemplate); } + return callback({ code: 500, msg: 'Template file ' + templatePath + ' had no content' }); }); }); @@ -775,6 +773,7 @@ const _templatesPath = function(basedir, module, template) { if (template) { templatePath += '/' + template; } + return templatePath; }; @@ -851,10 +850,4 @@ const _decr = function() { } }; -module.exports = { - init, - refreshTemplates, - sendEmail, - whenAllEmailsSent, - emitter: EmailAPI -}; +export { init, refreshTemplates, sendEmail, whenAllEmailsSent, EmailAPI as emitter }; diff --git a/packages/oae-email/lib/init.js b/packages/oae-email/lib/init.js index 5e58c6f5ed..6d677942d8 100644 --- a/packages/oae-email/lib/init.js +++ b/packages/oae-email/lib/init.js @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -const EmailAPI = require('oae-email'); +import * as EmailAPI from 'oae-email'; -module.exports = function(config, callback) { +export function init(config, callback) { EmailAPI.init(config.email, callback); -}; +} diff --git a/packages/oae-email/lib/test/util.js b/packages/oae-email/lib/test/util.js index ca077eee16..4471f3a1f6 100644 --- a/packages/oae-email/lib/test/util.js +++ b/packages/oae-email/lib/test/util.js @@ -13,18 +13,18 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const util = require('util'); -const _ = require('underscore'); +import assert from 'assert'; +import util from 'util'; +import _ from 'underscore'; -const ActivityAggregator = require('oae-activity/lib/internal/aggregator'); -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityEmail = require('oae-activity/lib/internal/email'); -const ActivityNotifications = require('oae-activity/lib/internal/notifications'); -const Cassandra = require('oae-util/lib/cassandra'); -const MqTestsUtil = require('oae-util/lib/test/mq-util'); +import * as ActivityAggregator from 'oae-activity/lib/internal/aggregator'; +import * as ActivityEmail from 'oae-activity/lib/internal/email'; +import * as ActivityNotifications from 'oae-activity/lib/internal/notifications'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as MqTestsUtil from 'oae-util/lib/test/mq-util'; +import * as EmailAPI from 'oae-email'; -const EmailAPI = require('oae-email'); +const { ActivityConstants } = require('oae-activity/lib/constants'); /** * Send and return a single email message. This helper utility will ensure that the activity / notifications queue @@ -189,10 +189,7 @@ const collectAndFetchAllEmails = function(callback) { ); assert.ok( line.split(' { // Rest context that can be used every time we need to make a request as an anonymous user @@ -103,91 +97,56 @@ describe('Emails', () => { mrvisser.user.email = 'blah blah blah'; // Verify error when there is invalid email - EmailTestsUtil.sendEmail( - 'oae-email', - 'test', - mrvisser.user, - null, - null, - (err, message) => { + EmailTestsUtil.sendEmail('oae-email', 'test', mrvisser.user, null, null, (err, message) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + + // Verify error when there is no user + EmailTestsUtil.sendEmail('oae-email', 'test', null, null, null, (err, message) => { assert.ok(err); assert.strictEqual(err.code, 400); - // Verify error when there is no user - EmailTestsUtil.sendEmail('oae-email', 'test', null, null, null, (err, message) => { + mrvisser.user.email = 'my.email@my.email.com'; + + // Verify error when there is no module + EmailTestsUtil.sendEmail(null, 'test', mrvisser.user, null, null, (err, message) => { assert.ok(err); assert.strictEqual(err.code, 400); - mrvisser.user.email = 'my.email@my.email.com'; + // Verify error when there is no template id + EmailTestsUtil.sendEmail('oae-email', null, mrvisser.user, null, null, (err, message) => { + assert.ok(err); + assert.strictEqual(err.code, 400); - // Verify error when there is no module - EmailTestsUtil.sendEmail( - null, - 'test', - mrvisser.user, - null, - null, - (err, message) => { + // Verify error with non-existent module + EmailTestsUtil.sendEmail('oae-non-existent', 'test', mrvisser.user, null, null, (err, message) => { assert.ok(err); - assert.strictEqual(err.code, 400); + assert.strictEqual(err.code, 500); - // Verify error when there is no template id + // Verify error with non-existent template id EmailTestsUtil.sendEmail( 'oae-email', - null, + 'TemplateDoesNotExist', mrvisser.user, null, null, (err, message) => { assert.ok(err); - assert.strictEqual(err.code, 400); - - // Verify error with non-existent module - EmailTestsUtil.sendEmail( - 'oae-non-existent', - 'test', - mrvisser.user, - null, - null, - (err, message) => { - assert.ok(err); - assert.strictEqual(err.code, 500); - - // Verify error with non-existent template id - EmailTestsUtil.sendEmail( - 'oae-email', - 'TemplateDoesNotExist', - mrvisser.user, - null, - null, - (err, message) => { - assert.ok(err); - assert.strictEqual(err.code, 500); - - // Sanity check - EmailTestsUtil.sendEmail( - 'oae-email', - 'test', - mrvisser.user, - null, - null, - (err, message) => { - assert.ok(!err); - assert.ok(message); - return callback(); - } - ); - } - ); - } - ); + assert.strictEqual(err.code, 500); + + // Sanity check + EmailTestsUtil.sendEmail('oae-email', 'test', mrvisser.user, null, null, (err, message) => { + assert.ok(!err); + assert.ok(message); + return callback(); + }); } ); - } - ); + }); + }); }); - } - ); + }); + }); }); }); }); @@ -208,39 +167,25 @@ describe('Emails', () => { nico.user.locale = 'fr_FR'; // Verify mrvisser gets the email - EmailTestsUtil.sendEmail( - 'oae-email', - 'test_locale', - mrvisser.user, - null, - null, - (err, message) => { - assert.ok(!err); - const mrvisserMessage = message; - assert.ok(mrvisserMessage.subject); - assert.ok(mrvisserMessage.text); + EmailTestsUtil.sendEmail('oae-email', 'test_locale', mrvisser.user, null, null, (err, message) => { + assert.ok(!err); + const mrvisserMessage = message; + assert.ok(mrvisserMessage.subject); + assert.ok(mrvisserMessage.text); - // Verify nico gets the email - EmailTestsUtil.sendEmail( - 'oae-email', - 'test_locale', - nico.user, - null, - null, - (err, message) => { - assert.ok(!err); - const nicoMessage = message; - assert.ok(nicoMessage.subject); - assert.ok(nicoMessage.text); + // Verify nico gets the email + EmailTestsUtil.sendEmail('oae-email', 'test_locale', nico.user, null, null, (err, message) => { + assert.ok(!err); + const nicoMessage = message; + assert.ok(nicoMessage.subject); + assert.ok(nicoMessage.text); - // Because of the locale difference, the subject and body of the mails should be different - assert.notStrictEqual(mrvisserMessage.subject, nicoMessage.subject); - assert.notStrictEqual(mrvisserMessage.text, nicoMessage.text); - return callback(); - } - ); - } - ); + // Because of the locale difference, the subject and body of the mails should be different + assert.notStrictEqual(mrvisserMessage.subject, nicoMessage.subject); + assert.notStrictEqual(mrvisserMessage.text, nicoMessage.text); + return callback(); + }); + }); }); }); @@ -256,19 +201,12 @@ describe('Emails', () => { mrvisser.user.email = 'mrvisser@email.address.com'; // Verify error when there is no meta template - EmailTestsUtil.sendEmail( - 'oae-email', - 'TestNoMeta', - mrvisser.user, - null, - null, - (err, message) => { - assert.ok(err); - assert.strictEqual(err.code, 500); - assert.strictEqual(err.msg.indexOf('No email metadata'), 0); - return callback(); - } - ); + EmailTestsUtil.sendEmail('oae-email', 'TestNoMeta', mrvisser.user, null, null, (err, message) => { + assert.ok(err); + assert.strictEqual(err.code, 500); + assert.strictEqual(err.msg.indexOf('No email metadata'), 0); + return callback(); + }); }); }); @@ -283,19 +221,12 @@ describe('Emails', () => { mrvisser.user.email = 'mrvisser@email.address.com'; // Verify error when there is no email - EmailTestsUtil.sendEmail( - 'oae-email', - 'test_meta_only', - mrvisser.user, - null, - null, - (err, message) => { - assert.ok(err); - assert.strictEqual(err.code, 500); - assert.strictEqual(err.msg.indexOf('No email content'), 0); - return callback(); - } - ); + EmailTestsUtil.sendEmail('oae-email', 'test_meta_only', mrvisser.user, null, null, (err, message) => { + assert.ok(err); + assert.strictEqual(err.code, 500); + assert.strictEqual(err.msg.indexOf('No email content'), 0); + return callback(); + }); }); }); @@ -310,74 +241,41 @@ describe('Emails', () => { mrvisser.user.email = 'mrvisser@email.address.com'; // Verify HTML only - EmailTestsUtil.sendEmail( - 'oae-email', - 'test_html_only', - mrvisser.user, - null, - null, - (err, message) => { + EmailTestsUtil.sendEmail('oae-email', 'test_html_only', mrvisser.user, null, null, (err, message) => { + assert.ok(!err); + + assert.strictEqual(message.from[0].name, 'Cambridge University Test'); + assert.strictEqual(message.from[0].address, util.format('noreply@%s', mrvisser.restContext.hostHeader)); + assert.strictEqual(message.subject, 'test html only'); + assert.strictEqual(message.to[0].address, mrvisser.user.email); + assert.strictEqual(message.html, 'test html only'); + assert.strictEqual(message.text, 'test html only'); + + // Verify text only + EmailTestsUtil.sendEmail('oae-email', 'test_txt_only', mrvisser.user, null, null, (err, message) => { assert.ok(!err); assert.strictEqual(message.from[0].name, 'Cambridge University Test'); - assert.strictEqual( - message.from[0].address, - util.format('noreply@%s', mrvisser.restContext.hostHeader) - ); - assert.strictEqual(message.subject, 'test html only'); + assert.strictEqual(message.from[0].address, util.format('noreply@%s', mrvisser.restContext.hostHeader)); + assert.strictEqual(message.subject, 'test txt only'); assert.strictEqual(message.to[0].address, mrvisser.user.email); - assert.strictEqual(message.html, 'test html only'); - assert.strictEqual(message.text, 'test html only'); - - // Verify text only - EmailTestsUtil.sendEmail( - 'oae-email', - 'test_txt_only', - mrvisser.user, - null, - null, - (err, message) => { - assert.ok(!err); + assert.ok(!message.html); + assert.strictEqual(message.text, '**test txt only**'); - assert.strictEqual(message.from[0].name, 'Cambridge University Test'); - assert.strictEqual( - message.from[0].address, - util.format('noreply@%s', mrvisser.restContext.hostHeader) - ); - assert.strictEqual(message.subject, 'test txt only'); - assert.strictEqual(message.to[0].address, mrvisser.user.email); - assert.ok(!message.html); - assert.strictEqual(message.text, '**test txt only**'); - - // Verify contents with both html and text - EmailTestsUtil.sendEmail( - 'oae-email', - 'test_html_and_txt', - mrvisser.user, - null, - null, - (err, message) => { - assert.ok(!err); + // Verify contents with both html and text + EmailTestsUtil.sendEmail('oae-email', 'test_html_and_txt', mrvisser.user, null, null, (err, message) => { + assert.ok(!err); - assert.strictEqual(message.from[0].name, 'Cambridge University Test'); - assert.strictEqual( - message.from[0].address, - util.format('noreply@%s', mrvisser.restContext.hostHeader) - ); - assert.strictEqual(message.subject, 'test html and txt'); - assert.strictEqual(message.to[0].address, mrvisser.user.email); - assert.strictEqual( - message.html, - 'test html and text' - ); - assert.strictEqual(message.text, '**test html and txt**'); - return callback(); - } - ); - } - ); - } - ); + assert.strictEqual(message.from[0].name, 'Cambridge University Test'); + assert.strictEqual(message.from[0].address, util.format('noreply@%s', mrvisser.restContext.hostHeader)); + assert.strictEqual(message.subject, 'test html and txt'); + assert.strictEqual(message.to[0].address, mrvisser.user.email); + assert.strictEqual(message.html, 'test html and text'); + assert.strictEqual(message.text, '**test html and txt**'); + return callback(); + }); + }); + }); }); }); @@ -489,10 +387,7 @@ describe('Emails', () => { (err, message) => { assert.ok(err); assert.strictEqual(err.code, 500); - assert.strictEqual( - err.msg.indexOf('Could not parse a suitable content template'), - 0 - ); + assert.strictEqual(err.msg.indexOf('Could not parse a suitable content template'), 0); return callback(); } ); @@ -515,19 +410,12 @@ describe('Emails', () => { const mrvisser = _.values(users)[0]; mrvisser.user.email = 'mrvisser@email.address.com'; - EmailTestsUtil.sendEmail( - 'oae-email', - 'test_shared', - mrvisser.user, - {}, - null, - (err, message) => { - assert.ok(!err); - assert.strictEqual(message.subject, 'foo'); - assert.strictEqual(message.text, 'bar'); - return callback(); - } - ); + EmailTestsUtil.sendEmail('oae-email', 'test_shared', mrvisser.user, {}, null, (err, message) => { + assert.ok(!err); + assert.strictEqual(message.subject, 'foo'); + assert.strictEqual(message.text, 'bar'); + return callback(); + }); }); }); @@ -538,19 +426,12 @@ describe('Emails', () => { TestsUtil.generateTestUsers(camAdminRestContext, 1, (err, users, mrvisser) => { assert.ok(!err); - EmailTestsUtil.sendEmail( - 'oae-email', - 'test_include', - mrvisser.user, - {}, - null, - (err, message) => { - assert.ok(!err); - assert.ok(message.html); - assert.ok(message.html.indexOf('') > -1); - return callback(); - } - ); + EmailTestsUtil.sendEmail('oae-email', 'test_include', mrvisser.user, {}, null, (err, message) => { + assert.ok(!err); + assert.ok(message.html); + assert.ok(message.html.indexOf('') > -1); + return callback(); + }); }); }); }); @@ -637,10 +518,7 @@ describe('Emails', () => { assert.ok(messages); assert.ok(messages.length); assert.strictEqual(messages[0].to[0].address, coenego.user.email); - assert.strictEqual( - messages[0].from[0].name, - global.oaeTests.tenants.cam.displayName - ); + assert.strictEqual(messages[0].from[0].name, global.oaeTests.tenants.cam.displayName); assert.strictEqual( messages[0].from[0].address, util.format('noreply@%s', global.oaeTests.tenants.cam.host) @@ -651,105 +529,80 @@ describe('Emails', () => { // eslint-disable-next-line no-template-curly-in-string 'oae-email/general/fromName': 'OAE for ${tenant}' }; - ConfigTestUtil.updateConfigAndWait( - camAdminRestContext, - null, - config, - err => { - assert.ok(!err); - - // Create a comment with the simong user. The coenego user will receive an email - RestAPI.Content.createComment( - simong.restContext, - link.id, - 'I am going to share this with all my friends!', - null, - (err, comment) => { - assert.ok(!err); - assert.ok(comment); - - // Assert that coenego receives an email with `"OAE for Cambridge University Test" ` - EmailTestsUtil.collectAndFetchAllEmails(messages => { - assert.ok(messages); - assert.ok(messages.length); - assert.strictEqual(messages[0].to[0].address, coenego.user.email); - assert.strictEqual( - messages[0].from[0].name, - 'OAE for ' + global.oaeTests.tenants.cam.displayName - ); - assert.strictEqual( - messages[0].from[0].address, - util.format('noreply@%s', global.oaeTests.tenants.cam.host) - ); - - // Sanity check that the from header can be configured on a global level - configToClear = [ - 'oae-email/general/fromAddress', - 'oae-email/general/fromName' - ]; - RestAPI.Config.clearConfig( - camAdminRestContext, - null, - configToClear, - err => { - assert.ok(!err); - const globalAdminRestContext = TestsUtil.createGlobalAdminRestContext(); - config = { - 'oae-email/general/fromName': - // eslint-disable-next-line no-template-curly-in-string - 'The glorious OAE for ${tenant}' - }; - ConfigTestUtil.updateConfigAndWait( - globalAdminRestContext, - null, - config, - err => { - assert.ok(!err); - - // Create a comment with the simong user. The coenego user will receive an email - RestAPI.Content.createComment( - simong.restContext, - link.id, - 'I am going to share this with all my friends!', - null, - (err, comment) => { - assert.ok(!err); - assert.ok(comment); - - // Assert that coenego receives an email with `"The glorious OAE for Cambridge University Test" ` - EmailTestsUtil.collectAndFetchAllEmails(messages => { - assert.ok(messages); - assert.ok(messages.length); - assert.strictEqual( - messages[0].to[0].address, - coenego.user.email - ); - assert.strictEqual( - messages[0].from[0].name, - 'The glorious OAE for ' + - global.oaeTests.tenants.cam.displayName - ); - assert.strictEqual( - messages[0].from[0].address, - util.format( - 'noreply@%s', - global.oaeTests.tenants.cam.host - ) - ); - - return callback(); - }); - } + ConfigTestUtil.updateConfigAndWait(camAdminRestContext, null, config, err => { + assert.ok(!err); + + // Create a comment with the simong user. The coenego user will receive an email + RestAPI.Content.createComment( + simong.restContext, + link.id, + 'I am going to share this with all my friends!', + null, + (err, comment) => { + assert.ok(!err); + assert.ok(comment); + + // Assert that coenego receives an email with `"OAE for Cambridge University Test" ` + EmailTestsUtil.collectAndFetchAllEmails(messages => { + assert.ok(messages); + assert.ok(messages.length); + assert.strictEqual(messages[0].to[0].address, coenego.user.email); + assert.strictEqual( + messages[0].from[0].name, + 'OAE for ' + global.oaeTests.tenants.cam.displayName + ); + assert.strictEqual( + messages[0].from[0].address, + util.format('noreply@%s', global.oaeTests.tenants.cam.host) + ); + + // Sanity check that the from header can be configured on a global level + configToClear = ['oae-email/general/fromAddress', 'oae-email/general/fromName']; + RestAPI.Config.clearConfig(camAdminRestContext, null, configToClear, err => { + assert.ok(!err); + const globalAdminRestContext = TestsUtil.createGlobalAdminRestContext(); + config = { + 'oae-email/general/fromName': + // eslint-disable-next-line no-template-curly-in-string + 'The glorious OAE for ${tenant}' + }; + ConfigTestUtil.updateConfigAndWait(globalAdminRestContext, null, config, err => { + assert.ok(!err); + + // Create a comment with the simong user. The coenego user will receive an email + RestAPI.Content.createComment( + simong.restContext, + link.id, + 'I am going to share this with all my friends!', + null, + (err, comment) => { + assert.ok(!err); + assert.ok(comment); + + // Assert that coenego receives an email with `"The glorious OAE for Cambridge University Test" ` + EmailTestsUtil.collectAndFetchAllEmails(messages => { + assert.ok(messages); + assert.ok(messages.length); + assert.strictEqual(messages[0].to[0].address, coenego.user.email); + assert.strictEqual( + messages[0].from[0].name, + 'The glorious OAE for ' + global.oaeTests.tenants.cam.displayName + ); + assert.strictEqual( + messages[0].from[0].address, + util.format('noreply@%s', global.oaeTests.tenants.cam.host) ); - } - ); - } - ); + + return callback(); + }); + } + ); + }); }); - } - ); - } - ); + }); + } + ); + }); }); } ); @@ -864,45 +717,38 @@ describe('Emails', () => { assert.ok(message); // Re-using the same hash should result in test failure - EmailTestsUtil.sendEmail( - 'oae-email', - 'test', - simong.user, - null, - { hash: 'u:cam:simong#123456' }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 403); - - // Re-using the same hash, but with the same mail should result in a failure - // We generate a "different" mail by passing in a data object - EmailTestsUtil.sendEmail( - 'oae-email', - 'test', - simong.user, - { data: 'test' }, - { hash: 'u:cam:simong#123456' }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 403); - - // Using another hash (but otherwise the same mail) should work - EmailTestsUtil.sendEmail( - 'oae-email', - 'test', - simong.user, - null, - { hash: 'u:cam:mrvisser#000000' }, - (err, message) => { - assert.ok(!err); - assert.ok(message); - return callback(); - } - ); - } - ); - } - ); + EmailTestsUtil.sendEmail('oae-email', 'test', simong.user, null, { hash: 'u:cam:simong#123456' }, err => { + assert.ok(err); + assert.strictEqual(err.code, 403); + + // Re-using the same hash, but with the same mail should result in a failure + // We generate a "different" mail by passing in a data object + EmailTestsUtil.sendEmail( + 'oae-email', + 'test', + simong.user, + { data: 'test' }, + { hash: 'u:cam:simong#123456' }, + err => { + assert.ok(err); + assert.strictEqual(err.code, 403); + + // Using another hash (but otherwise the same mail) should work + EmailTestsUtil.sendEmail( + 'oae-email', + 'test', + simong.user, + null, + { hash: 'u:cam:mrvisser#000000' }, + (err, message) => { + assert.ok(!err); + assert.ok(message); + return callback(); + } + ); + } + ); + }); } ); }); @@ -930,18 +776,11 @@ describe('Emails', () => { assert.strictEqual(err.code, 403); // Sanity check that sending out a different email works - EmailTestsUtil.sendEmail( - 'oae-email', - 'test', - simong.user, - { data: 'test' }, - null, - err => { - assert.ok(err); - assert.strictEqual(err.code, 403); - return callback(); - } - ); + EmailTestsUtil.sendEmail('oae-email', 'test', simong.user, { data: 'test' }, null, err => { + assert.ok(err); + assert.strictEqual(err.code, 403); + return callback(); + }); }); }); }); @@ -1008,29 +847,15 @@ describe('Emails', () => { assert.ok(message); // Sanity-check we cannot send it twice - EmailTestsUtil.sendEmail( - 'oae-email', - 'test', - simong.user, - null, - null, - (err, message) => { + EmailTestsUtil.sendEmail('oae-email', 'test', simong.user, null, null, (err, message) => { + assert.ok(err); + assert.strictEqual(err.code, 403); + EmailTestsUtil.sendEmail('oae-email', 'test', nico.user, null, null, (err, message) => { assert.ok(err); assert.strictEqual(err.code, 403); - EmailTestsUtil.sendEmail( - 'oae-email', - 'test', - nico.user, - null, - null, - (err, message) => { - assert.ok(err); - assert.strictEqual(err.code, 403); - return callback(); - } - ); - } - ); + return callback(); + }); + }); }); }); }); @@ -1051,52 +876,45 @@ describe('Emails', () => { // The user needs an email address simong.user.email = TestsUtil.generateTestEmailAddress(); - EmailTestsUtil.sendEmail( - 'oae-email', - 'test', - simong.user, - null, - { hash: _uniqueHash() }, - (err, message) => { - assert.ok(!err); - EmailTestsUtil.sendEmail( - 'oae-email', - 'test', - simong.user, - null, - { hash: _uniqueHash() }, - (err, message) => { - assert.ok(!err); - EmailTestsUtil.sendEmail( - 'oae-email', - 'test', - simong.user, - null, - { hash: _uniqueHash() }, - (err, message) => { - assert.ok(err); - assert.strictEqual(err.code, 403); - - // If we wait longer than the throttle timespan, we should be able to send an e-mail to this user - setTimeout(() => { - EmailTestsUtil.sendEmail( - 'oae-email', - 'test', - simong.user, - null, - { hash: _uniqueHash() }, - (err, message) => { - assert.ok(!err); - return callback(); - } - ); - }, 2250); - } - ); - } - ); - } - ); + EmailTestsUtil.sendEmail('oae-email', 'test', simong.user, null, { hash: _uniqueHash() }, (err, message) => { + assert.ok(!err); + EmailTestsUtil.sendEmail( + 'oae-email', + 'test', + simong.user, + null, + { hash: _uniqueHash() }, + (err, message) => { + assert.ok(!err); + EmailTestsUtil.sendEmail( + 'oae-email', + 'test', + simong.user, + null, + { hash: _uniqueHash() }, + (err, message) => { + assert.ok(err); + assert.strictEqual(err.code, 403); + + // If we wait longer than the throttle timespan, we should be able to send an e-mail to this user + setTimeout(() => { + EmailTestsUtil.sendEmail( + 'oae-email', + 'test', + simong.user, + null, + { hash: _uniqueHash() }, + (err, message) => { + assert.ok(!err); + return callback(); + } + ); + }, 2250); + } + ); + } + ); + }); }); }); }); diff --git a/packages/oae-emitter/lib/api.js b/packages/oae-emitter/lib/api.js index 6569f47036..02ecb033e3 100644 --- a/packages/oae-emitter/lib/api.js +++ b/packages/oae-emitter/lib/api.js @@ -13,11 +13,12 @@ * permissions and limitations under the License. */ -const events = require('events'); -const util = require('util'); -const _ = require('underscore'); +import events from 'events'; +import util from 'util'; +import _ from 'underscore'; +import { logger } from 'oae-logger'; -const log = require('oae-logger').logger('oae-emitter'); +const log = logger('oae-emitter'); /** * The OAE EventEmitter extends the core Node.js event emitter to allow chained event handling @@ -36,6 +37,7 @@ const EventEmitter = function() { events.EventEmitter.call(this); this._when = {}; }; + util.inherits(EventEmitter, events.EventEmitter); /** @@ -47,16 +49,14 @@ util.inherits(EventEmitter, events.EventEmitter); * @param {Function} [callback] Standard callback function. Invoked when all `when` handlers have completed their task */ EventEmitter.prototype.emit = function(...args) { - /* name, [args...], [callback] */ + /* Name, [args...], [callback] */ const self = this; - // const args = _.toArray(arguments); + // Const args = _.toArray(arguments); // The name is required and must be the first argument const name = args.shift(); if (!_.isString(name)) { - throw new TypeError( - util.format('Expected a string for event "name", but got: %s', JSON.stringify(name, null, 2)) - ); + throw new TypeError(util.format('Expected a string for event "name", but got: %s', JSON.stringify(name, null, 2))); } // First invoke the core event listeners @@ -135,6 +135,4 @@ EventEmitter.prototype.when = function(name, handler) { this._when[name].push(handler); }; -module.exports = { - EventEmitter -}; +export { EventEmitter }; diff --git a/packages/oae-emitter/tests/test-emitter.js b/packages/oae-emitter/tests/test-emitter.js index 49d93921c0..db4d1193b7 100644 --- a/packages/oae-emitter/tests/test-emitter.js +++ b/packages/oae-emitter/tests/test-emitter.js @@ -13,10 +13,9 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); - -const EmitterAPI = require('oae-emitter'); +import assert from 'assert'; +import _ from 'underscore'; +import * as EmitterAPI from 'oae-emitter'; const { EventEmitter } = EmitterAPI; diff --git a/packages/oae-folders/config/folder.js b/packages/oae-folders/config/folder.js index dc5078fc01..35d964643c 100644 --- a/packages/oae-folders/config/folder.js +++ b/packages/oae-folders/config/folder.js @@ -13,28 +13,26 @@ * permissions and limitations under the License. */ -var Fields = require('oae-config/lib/fields'); +import * as Fields from 'oae-config/lib/fields'; -module.exports = { - 'title': 'OAE Folders Module', - 'visibility': { - 'name': 'Default Visibility Values', - 'description': 'Default visibility setting for new folders', - 'elements': { - 'folder': new Fields.List('Folder Visibility', 'Default visibility for a new folder', 'public', [ - { - 'name': 'Public', - 'value': 'public' - }, - { - 'name': 'Authenticated Users', - 'value': 'loggedin' - }, - { - 'name': 'Private', - 'value': 'private' - } - ]) - } - } +export const title = 'OAE Folders Module'; +export const visibility = { + name: 'Default Visibility Values', + description: 'Default visibility setting for new folders', + elements: { + folder: new Fields.List('Folder Visibility', 'Default visibility for a new folder', 'public', [ + { + name: 'Public', + value: 'public' + }, + { + name: 'Authenticated Users', + value: 'loggedin' + }, + { + name: 'Private', + value: 'private' + } + ]) + } }; diff --git a/packages/oae-folders/lib/activity.js b/packages/oae-folders/lib/activity.js index e419a289e9..508f6aca99 100644 --- a/packages/oae-folders/lib/activity.js +++ b/packages/oae-folders/lib/activity.js @@ -13,25 +13,25 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); - -const ActivityAPI = require('oae-activity'); -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityModel = require('oae-activity/lib/model'); -const ActivityUtil = require('oae-activity/lib/util'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzUtil = require('oae-authz/lib/util'); -const { ContentConstants } = require('oae-content/lib/constants'); -const ContentUtil = require('oae-content/lib/internal/util'); -const MessageBoxAPI = require('oae-messagebox'); -const MessageBoxUtil = require('oae-messagebox/lib/util'); -const PreviewConstants = require('oae-preview-processor/lib/constants'); -const PrincipalsUtil = require('oae-principals/lib/util'); -const TenantsUtil = require('oae-tenants/lib/util'); - -const FoldersAPI = require('oae-folders'); -const { FoldersConstants } = require('oae-folders/lib/constants'); -const FoldersDAO = require('oae-folders/lib/internal/dao'); +import _ from 'underscore'; + +import * as ActivityAPI from 'oae-activity'; +import * as ActivityModel from 'oae-activity/lib/model'; +import * as ActivityUtil from 'oae-activity/lib/util'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ContentUtil from 'oae-content/lib/internal/util'; +import * as MessageBoxAPI from 'oae-messagebox'; +import * as MessageBoxUtil from 'oae-messagebox/lib/util'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; +import * as TenantsUtil from 'oae-tenants/lib/util'; +import * as FoldersAPI from 'oae-folders'; +import * as FoldersDAO from 'oae-folders/lib/internal/dao'; + +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { ActivityConstants } from 'oae-activity/lib/constants'; +import { ContentConstants } from 'oae-content/lib/constants'; +import { FoldersConstants } from 'oae-folders/lib/constants'; /// //////////////// // FOLDER-CREATE // @@ -126,35 +126,32 @@ ActivityAPI.registerActivityType(FoldersConstants.activity.ACTIVITY_FOLDER_ADD_T /*! * Post a folder-add-to-folder activity when a user adds content items to a folder */ -FoldersAPI.emitter.on( - FoldersConstants.events.ADDED_CONTENT_ITEMS, - (ctx, actionContext, folder, contentItems) => { - // Ignore activities triggered by content-create - if (actionContext !== 'content-create') { - const millis = Date.now(); - const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { - user: ctx.user() - }); - const targetResource = new ActivityModel.ActivitySeedResource('folder', folder.id, { - folder - }); - _.each(contentItems, content => { - const objectResource = new ActivityModel.ActivitySeedResource('content', content.id, { - content - }); - const activitySeed = new ActivityModel.ActivitySeed( - FoldersConstants.activity.ACTIVITY_FOLDER_ADD_TO_FOLDER, - millis, - ActivityConstants.verbs.ADD, - actorResource, - objectResource, - targetResource - ); - ActivityAPI.postActivity(ctx, activitySeed); +FoldersAPI.emitter.on(FoldersConstants.events.ADDED_CONTENT_ITEMS, (ctx, actionContext, folder, contentItems) => { + // Ignore activities triggered by content-create + if (actionContext !== 'content-create') { + const millis = Date.now(); + const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { + user: ctx.user() + }); + const targetResource = new ActivityModel.ActivitySeedResource('folder', folder.id, { + folder + }); + _.each(contentItems, content => { + const objectResource = new ActivityModel.ActivitySeedResource('content', content.id, { + content }); - } + const activitySeed = new ActivityModel.ActivitySeed( + FoldersConstants.activity.ACTIVITY_FOLDER_ADD_TO_FOLDER, + millis, + ActivityConstants.verbs.ADD, + actorResource, + objectResource, + targetResource + ); + ActivityAPI.postActivity(ctx, activitySeed); + }); } -); +}); /// //////////////////////////////////////////// // FOLDER-UPDATE and FOLDER-UPDATE-VISIBILITY// @@ -222,6 +219,7 @@ FoldersAPI.emitter.on(FoldersConstants.events.UPDATED_FOLDER, (ctx, oldFolder, u } else { activityType = FoldersConstants.activity.ACTIVITY_FOLDER_UPDATE_VISIBILITY; } + const activitySeed = new ActivityModel.ActivitySeed( activityType, millis, @@ -348,81 +346,72 @@ ActivityAPI.registerActivityType(FoldersConstants.activity.ACTIVITY_FOLDER_UPDAT } }); -FoldersAPI.emitter.on( - FoldersConstants.events.UPDATED_FOLDER_MEMBERS, - (ctx, folder, memberChangeInfo, opts) => { - if (opts.invitation) { - // If this member update came from an invitation, we bypass adding activity as there is a - // dedicated activity for that - return; - } +FoldersAPI.emitter.on(FoldersConstants.events.UPDATED_FOLDER_MEMBERS, (ctx, folder, memberChangeInfo, opts) => { + if (opts.invitation) { + // If this member update came from an invitation, we bypass adding activity as there is a + // dedicated activity for that + return; + } - const addedMemberIds = _.pluck(memberChangeInfo.members.added, 'id'); - const updatedMemberIds = _.pluck(memberChangeInfo.members.updated, 'id'); + const addedMemberIds = _.pluck(memberChangeInfo.members.added, 'id'); + const updatedMemberIds = _.pluck(memberChangeInfo.members.updated, 'id'); - const millis = Date.now(); - const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { - user: ctx.user() - }); - const folderResource = new ActivityModel.ActivitySeedResource('folder', folder.id, { folder }); - - // When a user is added, it is considered either a folder-share or a folder-add-to-library - // activity, depending on whether the added user is the current user in context - _.each(addedMemberIds, memberId => { - if (memberId === ctx.user().id) { - // Users can't "share" with themselves, they actually "add it to their library" - ActivityAPI.postActivity( - ctx, - new ActivityModel.ActivitySeed( - FoldersConstants.activity.ACTIVITY_FOLDER_ADD_TO_LIBRARY, - millis, - ActivityConstants.verbs.ADD, - actorResource, - folderResource - ) - ); - } else { - // A user shared a folder with some other user, fire the folder share activity - const principalResourceType = PrincipalsUtil.isGroup(memberId) ? 'group' : 'user'; - const principalResource = new ActivityModel.ActivitySeedResource( - principalResourceType, - memberId - ); - ActivityAPI.postActivity( - ctx, - new ActivityModel.ActivitySeed( - FoldersConstants.activity.ACTIVITY_FOLDER_SHARE, - millis, - ActivityConstants.verbs.SHARE, - actorResource, - folderResource, - principalResource - ) - ); - } - }); + const millis = Date.now(); + const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { + user: ctx.user() + }); + const folderResource = new ActivityModel.ActivitySeedResource('folder', folder.id, { folder }); - // When a user's role is updated, we fire a "folder-update-member-role" activity - _.each(updatedMemberIds, memberId => { - const principalResourceType = PrincipalsUtil.isGroup(memberId) ? 'group' : 'user'; - const principalResource = new ActivityModel.ActivitySeedResource( - principalResourceType, - memberId - ); + // When a user is added, it is considered either a folder-share or a folder-add-to-library + // activity, depending on whether the added user is the current user in context + _.each(addedMemberIds, memberId => { + if (memberId === ctx.user().id) { + // Users can't "share" with themselves, they actually "add it to their library" ActivityAPI.postActivity( ctx, new ActivityModel.ActivitySeed( - FoldersConstants.activity.ACTIVITY_FOLDER_UPDATE_MEMBER_ROLE, + FoldersConstants.activity.ACTIVITY_FOLDER_ADD_TO_LIBRARY, millis, - ActivityConstants.verbs.UPDATE, + ActivityConstants.verbs.ADD, actorResource, - principalResource, folderResource ) ); - }); - } -); + } else { + // A user shared a folder with some other user, fire the folder share activity + const principalResourceType = PrincipalsUtil.isGroup(memberId) ? 'group' : 'user'; + const principalResource = new ActivityModel.ActivitySeedResource(principalResourceType, memberId); + ActivityAPI.postActivity( + ctx, + new ActivityModel.ActivitySeed( + FoldersConstants.activity.ACTIVITY_FOLDER_SHARE, + millis, + ActivityConstants.verbs.SHARE, + actorResource, + folderResource, + principalResource + ) + ); + } + }); + + // When a user's role is updated, we fire a "folder-update-member-role" activity + _.each(updatedMemberIds, memberId => { + const principalResourceType = PrincipalsUtil.isGroup(memberId) ? 'group' : 'user'; + const principalResource = new ActivityModel.ActivitySeedResource(principalResourceType, memberId); + ActivityAPI.postActivity( + ctx, + new ActivityModel.ActivitySeed( + FoldersConstants.activity.ACTIVITY_FOLDER_UPDATE_MEMBER_ROLE, + millis, + ActivityConstants.verbs.UPDATE, + actorResource, + principalResource, + folderResource + ) + ); + }); +}); /// //////////////////////// // ACTIVITY ENTITY TYPES // @@ -433,8 +422,7 @@ FoldersAPI.emitter.on( * @see ActivityAPI#registerActivityEntityType */ const _folderProducer = function(resource, callback) { - const folder = - resource.resourceData && resource.resourceData.folder ? resource.resourceData.folder : null; + const folder = resource.resourceData && resource.resourceData.folder ? resource.resourceData.folder : null; // If the folder was fired with the resource, use it instead of fetching if (folder) { @@ -477,9 +465,11 @@ const _folderTransformer = function(ctx, activityEntities, callback) { _.each(activityEntities, (entitiesPerActivity, activityId) => { transformedActivityEntities[activityId] = transformedActivityEntities[activityId] || {}; _.each(entitiesPerActivity, (entity, entityId) => { - transformedActivityEntities[activityId][ - entityId - ] = _transformPersistentFolderActivityEntity(ctx, entity, foldersById); + transformedActivityEntities[activityId][entityId] = _transformPersistentFolderActivityEntity( + ctx, + entity, + foldersById + ); }); }); @@ -526,9 +516,7 @@ const _folderCommentTransformer = function(ctx, activityEntities, callback) { const resource = AuthzUtil.getResourceFromId(folderId); const profilePath = '/folder/' + resource.tenantAlias + '/' + resource.resourceId; const urlFormat = '/api/folder/' + folderId + '/messages/%s'; - transformedActivityEntities[activityId][ - entityId - ] = MessageBoxUtil.transformPersistentMessageActivityEntity( + transformedActivityEntities[activityId][entityId] = MessageBoxUtil.transformPersistentMessageActivityEntity( ctx, entity, profilePath, @@ -564,11 +552,7 @@ ActivityAPI.registerActivityEntityType('folder', { internal: _folderTransformer }, propagation(associationsCtx, entity, callback) { - ActivityUtil.getStandardResourcePropagation( - entity.folder.visibility, - AuthzConstants.joinable.NO, - callback - ); + ActivityUtil.getStandardResourcePropagation(entity.folder.visibility, AuthzConstants.joinable.NO, callback); } }); @@ -590,88 +574,56 @@ ActivityAPI.registerActivityEntityType('folder-comment', { /*! * Register an association that presents the folder */ -ActivityAPI.registerActivityEntityAssociation( - 'folder', - 'self', - (associationsCtx, entity, callback) => { - return callback(null, [entity[ActivityConstants.properties.OAE_ID]]); - } -); +ActivityAPI.registerActivityEntityAssociation('folder', 'self', (associationsCtx, entity, callback) => { + return callback(null, [entity[ActivityConstants.properties.OAE_ID]]); +}); /*! * Register an association that presents the members of a folder categorized by role */ -ActivityAPI.registerActivityEntityAssociation( - 'folder', - 'members-by-role', - (associationsCtx, entity, callback) => { - ActivityUtil.getAllAuthzMembersByRole( - entity[FoldersConstants.activity.PROP_OAE_GROUP_ID], - callback - ); - } -); +ActivityAPI.registerActivityEntityAssociation('folder', 'members-by-role', (associationsCtx, entity, callback) => { + ActivityUtil.getAllAuthzMembersByRole(entity[FoldersConstants.activity.PROP_OAE_GROUP_ID], callback); +}); /*! * Register an association that presents all the indirect members of a folder */ -ActivityAPI.registerActivityEntityAssociation( - 'folder', - 'members', - (associationsCtx, entity, callback) => { - associationsCtx.get('members-by-role', (err, membersByRole) => { - if (err) { - return callback(err); - } +ActivityAPI.registerActivityEntityAssociation('folder', 'members', (associationsCtx, entity, callback) => { + associationsCtx.get('members-by-role', (err, membersByRole) => { + if (err) { + return callback(err); + } - return callback(null, _.flatten(_.values(membersByRole))); - }); - } -); + return callback(null, _.flatten(_.values(membersByRole))); + }); +}); /*! * Register an association that presents all the managers of a content item */ -ActivityAPI.registerActivityEntityAssociation( - 'folder', - 'managers', - (associationsCtx, entity, callback) => { - associationsCtx.get('members-by-role', (err, membersByRole) => { - if (err) { - return callback(err); - } +ActivityAPI.registerActivityEntityAssociation('folder', 'managers', (associationsCtx, entity, callback) => { + associationsCtx.get('members-by-role', (err, membersByRole) => { + if (err) { + return callback(err); + } - return callback(null, membersByRole[AuthzConstants.role.MANAGER]); - }); - } -); + return callback(null, membersByRole[AuthzConstants.role.MANAGER]); + }); +}); /*! * Register an assocation that presents all the commenting contributors of a folder */ -ActivityAPI.registerActivityEntityAssociation( - 'folder', - 'message-contributors', - (associationsCtx, entity, callback) => { - MessageBoxAPI.getRecentContributions( - entity[ActivityConstants.properties.OAE_ID], - null, - 100, - callback - ); - } -); +ActivityAPI.registerActivityEntityAssociation('folder', 'message-contributors', (associationsCtx, entity, callback) => { + MessageBoxAPI.getRecentContributions(entity[ActivityConstants.properties.OAE_ID], null, 100, callback); +}); /*! * Register an association that presents the folder for a folder-comment entity */ -ActivityAPI.registerActivityEntityAssociation( - 'folder-comment', - 'self', - (associationsCtx, entity, callback) => { - return callback(null, [entity.folderId]); - } -); +ActivityAPI.registerActivityEntityAssociation('folder-comment', 'self', (associationsCtx, entity, callback) => { + return callback(null, [entity.folderId]); +}); /** * Create the persistent folder entity that can be transformed into an activity entity for the UI. @@ -724,6 +676,7 @@ const _transformPersistentFolderActivityEntity = function(ctx, entity, foldersBy PreviewConstants.SIZES.IMAGE.THUMBNAIL ); } + if (folder.previews && folder.previews.wideUri) { const wideUrl = ContentUtil.getSignedDownloadUrl(ctx, folder.previews.wideUri, -1); opts.ext[ContentConstants.activity.PROP_OAE_WIDE_IMAGE] = new ActivityModel.ActivityMediaLink( diff --git a/packages/oae-folders/lib/api.js b/packages/oae-folders/lib/api.js index 851e771fcf..503e861d80 100644 --- a/packages/oae-folders/lib/api.js +++ b/packages/oae-folders/lib/api.js @@ -13,36 +13,41 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); - -const AuthzAPI = require('oae-authz'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzInvitations = require('oae-authz/lib/invitations'); -const AuthzPermissions = require('oae-authz/lib/permissions'); -const ContentAPI = require('oae-content'); -const ContentDAO = require('oae-content/lib/internal/dao'); -const ContentUtil = require('oae-content/lib/internal/util'); -const EmitterAPI = require('oae-emitter'); -const LibraryAPI = require('oae-library'); -const log = require('oae-logger').logger('oae-folders-api'); -const MessageBoxAPI = require('oae-messagebox'); -const { MessageBoxConstants } = require('oae-messagebox/lib/constants'); -const OaeUtil = require('oae-util/lib/util'); -const PrincipalsAPI = require('oae-principals'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const PrincipalsUtil = require('oae-principals/lib/util'); -const ResourceActions = require('oae-resource/lib/actions'); -const SearchAPI = require('oae-search'); -const Signature = require('oae-util/lib/signature'); -const { Validator } = require('oae-util/lib/validator'); - -const FoldersConfig = require('oae-config').config('oae-folders'); -const FoldersFoldersLibrary = require('./internal/foldersLibrary'); -const FoldersAuthz = require('./authz'); -const { FoldersConstants } = require('./constants'); -const FoldersContentLibrary = require('./internal/contentLibrary'); -const FoldersDAO = require('./internal/dao'); +import util from 'util'; +import _ from 'underscore'; +import { logger } from 'oae-logger'; +import { setUpConfig } from 'oae-config'; + +import * as AuthzAPI from 'oae-authz'; +import * as AuthzInvitations from 'oae-authz/lib/invitations'; +import * as AuthzPermissions from 'oae-authz/lib/permissions'; +import * as ContentAPI from 'oae-content'; +import * as ContentDAO from 'oae-content/lib/internal/dao'; +import * as ContentUtil from 'oae-content/lib/internal/util'; +import * as EmitterAPI from 'oae-emitter'; +import * as LibraryAPI from 'oae-library'; + +import * as MessageBoxAPI from 'oae-messagebox'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as PrincipalsAPI from 'oae-principals'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; +import * as ResourceActions from 'oae-resource/lib/actions'; +import * as SearchAPI from 'oae-search'; +import * as Signature from 'oae-util/lib/signature'; +import { MessageBoxConstants } from 'oae-messagebox/lib/constants'; +import { Validator } from 'oae-util/lib/validator'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as FoldersFoldersLibrary from './internal/foldersLibrary'; +import * as FoldersAuthz from './authz'; +import * as FoldersContentLibrary from './internal/contentLibrary'; +import * as FoldersDAO from './internal/dao'; + +import { FoldersConstants } from './constants'; + +const log = logger('oae-folders-api'); + +const FoldersConfig = setUpConfig('oae-folders'); /*! * ### Events @@ -79,12 +84,8 @@ const createFolder = function(ctx, displayName, description, visibility, roles, // Verify basic properties const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'Anonymous users cannot create a folder' }) - .isLoggedInUser(ctx); - validator - .check(displayName, { code: 400, msg: 'Must provide a display name for the folder' }) - .notEmpty(); + validator.check(null, { code: 401, msg: 'Anonymous users cannot create a folder' }).isLoggedInUser(ctx); + validator.check(displayName, { code: 400, msg: 'Must provide a display name for the folder' }).notEmpty(); validator .check(displayName, { code: 400, msg: 'A display name can be at most 1000 characters long' }) .isShortString(); @@ -93,12 +94,11 @@ const createFolder = function(ctx, displayName, description, visibility, roles, .check(description, { code: 400, msg: 'A description can be at most 10000 characters long' }) .isMediumString(); } + validator .check(visibility, { code: 400, - msg: - 'An invalid folder visibility option has been provided. Must be one of: ' + - allVisibilities.join(', ') + msg: 'An invalid folder visibility option has been provided. Must be one of: ' + allVisibilities.join(', ') }) .isIn(allVisibilities); @@ -127,40 +127,30 @@ const createFolder = function(ctx, displayName, description, visibility, roles, if (err && err.code !== 404) { return callback(err); } + if (err) { return callback({ code: 400, msg: 'One or more target principals could not be found' }); } + if (!canManageAny) { // We only make the current user a manager of the folder if they cannot // manage any of the specified managers roles[ctx.user().id] = AuthzConstants.role.MANAGER; } - const createFn = _.partial( - FoldersDAO.createFolder, - ctx.user().id, - displayName, - description, - visibility - ); + const createFn = _.partial(FoldersDAO.createFolder, ctx.user().id, displayName, description, visibility); ResourceActions.create(ctx, roles, createFn, (err, folder, memberChangeInfo) => { if (err) { return callback(err); } - FoldersAPI.emit( - FoldersConstants.events.CREATED_FOLDER, - ctx, - folder, - memberChangeInfo, - errs => { - if (errs) { - return callback(_.first(errs)); - } - - return callback(null, folder); + FoldersAPI.emit(FoldersConstants.events.CREATED_FOLDER, ctx, folder, memberChangeInfo, errs => { + if (errs) { + return callback(_.first(errs)); } - ); + + return callback(null, folder); + }); }); }); }; @@ -182,9 +172,7 @@ const updateFolder = function(ctx, folderId, updates, callback) { const allVisibilities = _.values(AuthzConstants.visibility); const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'Anonymous users cannot create a folder' }) - .isLoggedInUser(ctx); + validator.check(null, { code: 401, msg: 'Anonymous users cannot create a folder' }).isLoggedInUser(ctx); validator .check(folderId, { code: 400, @@ -204,9 +192,7 @@ const updateFolder = function(ctx, folderId, updates, callback) { .min(1); _.each(updates, (val, key) => { - validator - .check(key, { code: 400, msg: 'Unknown update field provided' }) - .isIn(legalUpdateFields); + validator.check(key, { code: 400, msg: 'Unknown update field provided' }).isIn(legalUpdateFields); }); if (updates.displayName) { @@ -217,6 +203,7 @@ const updateFolder = function(ctx, folderId, updates, callback) { }) .isShortString(); } + if (updates.description) { validator .check(updates.description, { @@ -225,13 +212,12 @@ const updateFolder = function(ctx, folderId, updates, callback) { }) .isMediumString(); } + if (updates.visibility) { validator .check(updates.visibility, { code: 400, - msg: - 'An invalid folder visibility option has been provided. Must be one of: ' + - allVisibilities.join(', ') + msg: 'An invalid folder visibility option has been provided. Must be one of: ' + allVisibilities.join(', ') }) .isIn(allVisibilities); } @@ -297,9 +283,7 @@ const updateFolderContentVisibility = function(ctx, folderId, visibility, callba validator .check(visibility, { code: 400, - msg: - 'An invalid folder visibility option has been provided. Must be one of: ' + - allVisibilities.join(', ') + msg: 'An invalid folder visibility option has been provided. Must be one of: ' + allVisibilities.join(', ') }) .isIn(allVisibilities); if (validator.hasErrors()) { @@ -350,20 +334,14 @@ const _updateFolderContentVisibility = function(ctx, folder, visibility, callbac // Get all the content items in this folder FoldersAuthz.getContentInFolder(folder, (err, contentIds) => { if (err) { - log().error( - { err, folderId: folder.id }, - 'Got an error when updating the visibility of content in a folder' - ); + log().error({ err, folderId: folder.id }, 'Got an error when updating the visibility of content in a folder'); return callback(err); } // Get the content objects ContentDAO.Content.getMultipleContentItems(contentIds, null, (err, contentItems) => { if (err) { - log().error( - { err, folderId: folder.id }, - 'Got an error when updating the visibility of content in a folder' - ); + log().error({ err, folderId: folder.id }, 'Got an error when updating the visibility of content in a folder'); return callback(err); } @@ -575,6 +553,7 @@ const _getFullFolderProfile = function(ctx, folder, callback) { if (err) { return callback(err); } + if (!permissions.canView) { return callback({ code: 401, msg: 'You are not authorized to view this folder' }); } @@ -627,9 +606,7 @@ const _getFullFolderProfile = function(ctx, folder, callback) { const deleteFolder = function(ctx, folderId, deleteContent, callback) { const validator = new Validator(); validator.check(folderId, { code: 400, msg: 'A folder id must be provided' }).isResourceId(); - validator - .check(null, { code: 401, msg: 'You must be authenticated to delete a folder' }) - .isLoggedInUser(ctx); + validator.check(null, { code: 401, msg: 'You must be authenticated to delete a folder' }).isLoggedInUser(ctx); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -662,30 +639,26 @@ const deleteFolder = function(ctx, folderId, deleteContent, callback) { if (deleteContent) { _deleteContent(ctx, contentIds, failedContent => { // Get the content objects that we couldn't delete - ContentDAO.Content.getMultipleContentItems( - failedContent, - null, - (err, contentItems) => { - if (err) { - return callback(err); - } - - _.chain(contentItems) - // Remove null content items. This can happen if libraries are in an inconsistent - // state. For example, if an item was deleted from the system but hasn't been removed - // from the libraries, a `null` value would be returned by `getMultipleContentItems` - .compact() - - // Sign the content items, note that we don't have to do any permission - // checks here, as the user had access to these content items by virtue - // of being a member of the folder - .each(contentItem => { - ContentUtil.augmentContent(ctx, contentItem); - }); - - return callback(null, contentItems); + ContentDAO.Content.getMultipleContentItems(failedContent, null, (err, contentItems) => { + if (err) { + return callback(err); } - ); + + _.chain(contentItems) + // Remove null content items. This can happen if libraries are in an inconsistent + // state. For example, if an item was deleted from the system but hasn't been removed + // from the libraries, a `null` value would be returned by `getMultipleContentItems` + .compact() + + // Sign the content items, note that we don't have to do any permission + // checks here, as the user had access to these content items by virtue + // of being a member of the folder + .each(contentItem => { + ContentUtil.augmentContent(ctx, contentItem); + }); + + return callback(null, contentItems); + }); }); // Otherwise remove the folder as an authz member of @@ -820,6 +793,7 @@ const _removeAuthzFolderFromContentItems = function(folder, contentIds, callback 'Unable to remove the folder from a group' ); } + done(); }); }); @@ -890,9 +864,7 @@ const getFolderMembers = function(ctx, folderId, start, limit, callback) { */ const getFolderInvitations = function(ctx, folderId, callback) { const validator = new Validator(); - validator - .check(folderId, { code: 400, msg: 'A valid resource id must be specified' }) - .isResourceId(); + validator.check(folderId, { code: 400, msg: 'A valid resource id must be specified' }).isResourceId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -917,9 +889,7 @@ const getFolderInvitations = function(ctx, folderId, callback) { */ const resendFolderInvitation = function(ctx, folderId, email, callback) { const validator = new Validator(); - validator - .check(folderId, { code: 400, msg: 'A valid resource id must be specified' }) - .isResourceId(); + validator.check(folderId, { code: 400, msg: 'A valid resource id must be specified' }).isResourceId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -949,9 +919,7 @@ const shareFolder = function(ctx, folderId, principalIds, callback) { validator .check(null, { code: 401, msg: 'You have to be logged in to be able to share a folder' }) .isLoggedInUser(ctx); - validator - .check(folderId, { code: 400, msg: 'A valid folder id must be provided' }) - .isResourceId(); + validator.check(folderId, { code: 400, msg: 'A valid folder id must be provided' }).isResourceId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -963,36 +931,24 @@ const shareFolder = function(ctx, folderId, principalIds, callback) { } // Perform the share operation - ResourceActions.share( - ctx, - folder, - principalIds, - AuthzConstants.role.VIEWER, - (err, memberChangeInfo) => { - if (err) { - return callback(err); - } - if (_.isEmpty(memberChangeInfo.changes)) { - // If no new members were actually added, we don't have to do anything more - return callback(); - } - - FoldersAPI.emit( - FoldersConstants.events.UPDATED_FOLDER_MEMBERS, - ctx, - folder, - memberChangeInfo, - {}, - errs => { - if (errs) { - return callback(_.first(errs)); - } + ResourceActions.share(ctx, folder, principalIds, AuthzConstants.role.VIEWER, (err, memberChangeInfo) => { + if (err) { + return callback(err); + } - return callback(); - } - ); + if (_.isEmpty(memberChangeInfo.changes)) { + // If no new members were actually added, we don't have to do anything more + return callback(); } - ); + + FoldersAPI.emit(FoldersConstants.events.UPDATED_FOLDER_MEMBERS, ctx, folder, memberChangeInfo, {}, errs => { + if (errs) { + return callback(_.first(errs)); + } + + return callback(); + }); + }); }); }; @@ -1015,9 +971,7 @@ const setFolderPermissions = function(ctx, folderId, changes, callback) { msg: 'You have to be logged in to be able to change folder permissions' }) .isLoggedInUser(ctx); - validator - .check(folderId, { code: 400, msg: 'A valid folder id must be provided' }) - .isResourceId(); + validator.check(folderId, { code: 400, msg: 'A valid folder id must be provided' }).isResourceId(); // eslint-disable-next-line no-unused-vars _.each(changes, (role, principalId) => { validator @@ -1056,24 +1010,18 @@ const setFolderPermissions = function(ctx, folderId, changes, callback) { if (err) { return callback(err); } + if (_.isEmpty(memberChangeInfo.changes)) { return callback(); } - FoldersAPI.emit( - FoldersConstants.events.UPDATED_FOLDER_MEMBERS, - ctx, - folder, - memberChangeInfo, - {}, - errs => { - if (errs) { - return callback(_.first(errs)); - } - - return callback(); + FoldersAPI.emit(FoldersConstants.events.UPDATED_FOLDER_MEMBERS, ctx, folder, memberChangeInfo, {}, errs => { + if (errs) { + return callback(_.first(errs)); } - ); + + return callback(); + }); }); }); }; @@ -1095,12 +1043,8 @@ const addContentItemsToFolder = function(ctx, folderId, contentIds, callback) { msg: 'You have to be authenticated to be able to add an item to a folder' }) .isLoggedInUser(ctx); - validator - .check(folderId, { code: 400, msg: 'A valid folder id must be provided' }) - .isResourceId(); - validator - .check(null, { code: 400, msg: 'Must specify at least one content item to add' }) - .isArray(contentIds); + validator.check(folderId, { code: 400, msg: 'A valid folder id must be provided' }).isResourceId(); + validator.check(null, { code: 400, msg: 'Must specify at least one content item to add' }).isArray(contentIds); validator .check(_.values(contentIds).length, { code: 400, @@ -1148,6 +1092,7 @@ const addContentItemsToFolder = function(ctx, folderId, contentIds, callback) { if (err && err.code !== 401) { return callback(err); } + if (err && !_.isEmpty(err.invalidContentIds)) { return callback({ code: 401, @@ -1157,18 +1102,13 @@ const addContentItemsToFolder = function(ctx, folderId, contentIds, callback) { ) }); } + if (err) { return callback(err); } // Add all the items to the folder - return _addContentItemsToFolderLibrary( - ctx, - 'add-to-folder', - folder, - contentItems.slice(), - callback - ); + return _addContentItemsToFolderLibrary(ctx, 'add-to-folder', folder, contentItems.slice(), callback); }); }); }); @@ -1185,13 +1125,7 @@ const addContentItemsToFolder = function(ctx, folderId, contentIds, callback) { * @param {Object} callback.err An error object, if any * @api private */ -const _addContentItemsToFolderLibrary = function( - ctx, - actionContext, - folder, - contentItems, - callback -) { +const _addContentItemsToFolderLibrary = function(ctx, actionContext, folder, contentItems, callback) { // First, make the folder a member of all the content items _addContentItemsToAuthzFolder(folder, contentItems.slice(), err => { if (err) { @@ -1211,13 +1145,7 @@ const _addContentItemsToFolderLibrary = function( ); } - FoldersAPI.emit( - FoldersConstants.events.ADDED_CONTENT_ITEMS, - ctx, - actionContext, - folder, - contentItems - ); + FoldersAPI.emit(FoldersConstants.events.ADDED_CONTENT_ITEMS, ctx, actionContext, folder, contentItems); return callback(err); }); @@ -1241,12 +1169,8 @@ const removeContentItemsFromFolder = function(ctx, folderId, contentIds, callbac msg: 'You have to be authenticated to be able to remove an item from a folder' }) .isLoggedInUser(ctx); - validator - .check(folderId, { code: 400, msg: 'A valid folder id must be provided' }) - .isResourceId(); - validator - .check(null, { code: 400, msg: 'You must specify at least one content item to remove' }) - .isArray(contentIds); + validator.check(folderId, { code: 400, msg: 'A valid folder id must be provided' }).isResourceId(); + validator.check(null, { code: 400, msg: 'You must specify at least one content item to remove' }).isArray(contentIds); validator .check(_.values(contentIds).length, { code: 400, @@ -1313,12 +1237,7 @@ const removeContentItemsFromFolder = function(ctx, folderId, contentIds, callbac ); } - FoldersAPI.emit( - FoldersConstants.events.REMOVED_CONTENT_ITEMS, - ctx, - folder, - contentItems - ); + FoldersAPI.emit(FoldersConstants.events.REMOVED_CONTENT_ITEMS, ctx, folder, contentItems); return callback(); }); }); @@ -1343,9 +1262,7 @@ const getFoldersLibrary = function(ctx, principalId, start, limit, callback) { limit = OaeUtil.getNumberParam(limit, 10, 1); const validator = new Validator(); - validator - .check(principalId, { code: 400, msg: 'A user or group id must be provided' }) - .isPrincipalId(); + validator.check(principalId, { code: 400, msg: 'A user or group id must be provided' }).isPrincipalId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -1357,55 +1274,46 @@ const getFoldersLibrary = function(ctx, principalId, start, limit, callback) { } // Determine which library visibility the current user should receive - LibraryAPI.Authz.resolveTargetLibraryAccess( - ctx, - principal.id, - principal, - (err, hasAccess, visibility) => { + LibraryAPI.Authz.resolveTargetLibraryAccess(ctx, principal.id, principal, (err, hasAccess, visibility) => { + if (err) { + return callback(err); + } + + if (!hasAccess) { + return callback({ code: 401, msg: 'You do not have have access to this library' }); + } + + // Get the folder ids from the library index + FoldersFoldersLibrary.list(principal, visibility, { start, limit }, (err, folderIds, nextToken) => { if (err) { return callback(err); } - if (!hasAccess) { - return callback({ code: 401, msg: 'You do not have have access to this library' }); - } - // Get the folder ids from the library index - FoldersFoldersLibrary.list( - principal, - visibility, - { start, limit }, - (err, folderIds, nextToken) => { - if (err) { - return callback(err); - } - - // Get the folder objects from the folderIds - FoldersDAO.getFoldersByIds(folderIds, (err, folders) => { - if (err) { - return callback(err); - } + // Get the folder objects from the folderIds + FoldersDAO.getFoldersByIds(folderIds, (err, folders) => { + if (err) { + return callback(err); + } - folders = _.map(folders, folder => { - return _augmentFolder(ctx, folder); - }); + folders = _.map(folders, folder => { + return _augmentFolder(ctx, folder); + }); - // Emit an event indicating that the folder library has been retrieved - FoldersAPI.emit( - FoldersConstants.events.GET_FOLDERS_LIBRARY, - ctx, - principalId, - visibility, - start, - limit, - folders - ); + // Emit an event indicating that the folder library has been retrieved + FoldersAPI.emit( + FoldersConstants.events.GET_FOLDERS_LIBRARY, + ctx, + principalId, + visibility, + start, + limit, + folders + ); - return callback(null, folders, nextToken); - }); - } - ); - } - ); + return callback(null, folders, nextToken); + }); + }); + }); }); }; @@ -1476,12 +1384,8 @@ const removeFolderFromLibrary = function(ctx, principalId, folderId, callback) { validator .check(null, { code: 401, msg: 'You must be authenticated to remove a folder from a library' }) .isLoggedInUser(ctx); - validator - .check(principalId, { code: 400, msg: 'A user or group id must be provided' }) - .isPrincipalId(); - validator - .check(folderId, { code: 400, msg: 'A valid folder id must be provided' }) - .isResourceId(); + validator.check(principalId, { code: 400, msg: 'A user or group id must be provided' }).isPrincipalId(); + validator.check(folderId, { code: 400, msg: 'A valid folder id must be provided' }).isResourceId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -1510,20 +1414,13 @@ const removeFolderFromLibrary = function(ctx, principalId, folderId, callback) { return callback(err); } - FoldersAPI.emit( - FoldersConstants.events.UPDATED_FOLDER_MEMBERS, - ctx, - folder, - memberChangeInfo, - {}, - errs => { - if (errs) { - return callback(_.first(errs)); - } - - return callback(); + FoldersAPI.emit(FoldersConstants.events.UPDATED_FOLDER_MEMBERS, ctx, folder, memberChangeInfo, {}, errs => { + if (errs) { + return callback(_.first(errs)); } - ); + + return callback(); + }); }); }); }); @@ -1558,50 +1455,41 @@ const getFolderContentLibrary = function(ctx, folderId, start, limit, callback) } // Determine which library visibility the current user should receive - LibraryAPI.Authz.resolveTargetLibraryAccess( - ctx, - folder.groupId, - folder, - (err, hasAccess, visibility) => { + LibraryAPI.Authz.resolveTargetLibraryAccess(ctx, folder.groupId, folder, (err, hasAccess, visibility) => { + if (err) { + return callback(err); + } + + if (!hasAccess) { + return callback({ code: 401, msg: 'You do not have access to this folder' }); + } + + FoldersContentLibrary.list(folder, visibility, { start, limit }, (err, contentIds, nextToken) => { if (err) { return callback(err); } - if (!hasAccess) { - return callback({ code: 401, msg: 'You do not have access to this folder' }); - } - - FoldersContentLibrary.list( - folder, - visibility, - { start, limit }, - (err, contentIds, nextToken) => { - if (err) { - return callback(err); - } - ContentDAO.Content.getMultipleContentItems(contentIds, null, (err, contentItems) => { - if (err) { - return callback(err); - } + ContentDAO.Content.getMultipleContentItems(contentIds, null, (err, contentItems) => { + if (err) { + return callback(err); + } - contentItems = _.chain(contentItems) - // Remove null content items. This can happen if libraries are in an inconsistent - // state. For example, if an item was deleted from the system but hasn't been removed - // from the libraries, a `null` value would be returned by `getMultipleContentItems` - .compact() + contentItems = _.chain(contentItems) + // Remove null content items. This can happen if libraries are in an inconsistent + // state. For example, if an item was deleted from the system but hasn't been removed + // from the libraries, a `null` value would be returned by `getMultipleContentItems` + .compact() - // Augment each content item with its signed preview urls - .each(contentItem => { - ContentUtil.augmentContent(ctx, contentItem); - }) - .value(); + // Augment each content item with its signed preview urls + .each(contentItem => { + ContentUtil.augmentContent(ctx, contentItem); + }) + .value(); - return callback(null, contentItems, nextToken); - }); - } - ); - } - ); + return callback(null, contentItems, nextToken); + }); + }); + }); }); }; @@ -1623,19 +1511,14 @@ const getFolderContentLibrary = function(ctx, folderId, start, limit, callback) */ const createMessage = function(ctx, folderId, body, replyToCreatedTimestamp, callback) { const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'Only authenticated users can post to folders' }) - .isLoggedInUser(ctx); + validator.check(null, { code: 401, msg: 'Only authenticated users can post to folders' }).isLoggedInUser(ctx); validator.check(folderId, { code: 400, msg: 'Invalid folder id provided' }).isResourceId(); validator.check(body, { code: 400, msg: 'A message body must be provided' }).notEmpty(); - validator - .check(body, { code: 400, msg: 'A message body can only be 100000 characters long' }) - .isLongString(); + validator.check(body, { code: 400, msg: 'A message body can only be 100000 characters long' }).isLongString(); if (replyToCreatedTimestamp) { - validator - .check(replyToCreatedTimestamp, { code: 400, msg: 'Invalid reply-to timestamp provided' }) - .isInt(); + validator.check(replyToCreatedTimestamp, { code: 400, msg: 'Invalid reply-to timestamp provided' }).isInt(); } + if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -1699,9 +1582,7 @@ const createMessage = function(ctx, folderId, body, replyToCreatedTimestamp, cal */ const deleteMessage = function(ctx, folderId, messageCreatedDate, callback) { const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'Only authenticated users can delete messages' }) - .isLoggedInUser(ctx); + validator.check(null, { code: 401, msg: 'Only authenticated users can delete messages' }).isLoggedInUser(ctx); validator.check(folderId, { code: 400, msg: 'A folder id must be provided' }).isResourceId(); validator .check(messageCreatedDate, { @@ -1720,54 +1601,45 @@ const deleteMessage = function(ctx, folderId, messageCreatedDate, callback) { } // Ensure that the message exists. We also need it so we can make sure we have access to delete it - MessageBoxAPI.getMessages( - folderId, - [messageCreatedDate], - { scrubDeleted: false }, - (err, messages) => { + MessageBoxAPI.getMessages(folderId, [messageCreatedDate], { scrubDeleted: false }, (err, messages) => { + if (err) { + return callback(err); + } + + if (!messages[0]) { + return callback({ code: 404, msg: 'The specified message does not exist' }); + } + + const message = messages[0]; + + // Determine if we have access to delete the folder message + AuthzPermissions.canManageMessage(ctx, folder, message, err => { if (err) { return callback(err); } - if (!messages[0]) { - return callback({ code: 404, msg: 'The specified message does not exist' }); - } - const message = messages[0]; - - // Determine if we have access to delete the folder message - AuthzPermissions.canManageMessage(ctx, folder, message, err => { - if (err) { - return callback(err); - } - - // Delete the message using the "leaf" method, which will SOFT delete if the message has replies, or HARD delete if it does not - MessageBoxAPI.deleteMessage( - folderId, - messageCreatedDate, - { deleteType: MessageBoxConstants.deleteTypes.LEAF }, - (err, deleteType, deletedMessage) => { - if (err) { - return callback(err); - } + // Delete the message using the "leaf" method, which will SOFT delete if the message has replies, or HARD delete if it does not + MessageBoxAPI.deleteMessage( + folderId, + messageCreatedDate, + { deleteType: MessageBoxConstants.deleteTypes.LEAF }, + (err, deleteType, deletedMessage) => { + if (err) { + return callback(err); + } - FoldersAPI.emit( - FoldersConstants.events.DELETED_COMMENT, - ctx, - message, - folder, - deleteType - ); + FoldersAPI.emit(FoldersConstants.events.DELETED_COMMENT, ctx, message, folder, deleteType); - // If a soft-delete occurred, we want to inform the consumer of the soft-delete message model - if (deleteType === MessageBoxConstants.deleteTypes.SOFT) { - return callback(null, deletedMessage); - } - return callback(); + // If a soft-delete occurred, we want to inform the consumer of the soft-delete message model + if (deleteType === MessageBoxConstants.deleteTypes.SOFT) { + return callback(null, deletedMessage); } - ); - }); - } - ); + + return callback(); + } + ); + }); + }); }); }; @@ -1806,42 +1678,36 @@ const getMessages = function(ctx, folderId, start, limit, callback) { } // Fetch the messages from the message box - MessageBoxAPI.getMessagesFromMessageBox( - folderId, - start, - limit, - null, - (err, messages, nextToken) => { + MessageBoxAPI.getMessagesFromMessageBox(folderId, start, limit, null, (err, messages, nextToken) => { + if (err) { + return callback(err); + } + + // Get the unique user ids from the messages so we can retrieve their full user objects + const userIds = _.chain(messages) + .map(message => { + return message.createdBy; + }) + .uniq() + .compact() + .value(); + + // Get the basic principal profiles of the messagers + PrincipalsUtil.getPrincipals(ctx, userIds, (err, users) => { if (err) { return callback(err); } - // Get the unique user ids from the messages so we can retrieve their full user objects - const userIds = _.chain(messages) - .map(message => { - return message.createdBy; - }) - .uniq() - .compact() - .value(); - - // Get the basic principal profiles of the messagers - PrincipalsUtil.getPrincipals(ctx, userIds, (err, users) => { - if (err) { - return callback(err); + // Attach the user profiles to the message objects + _.each(messages, message => { + if (users[message.createdBy]) { + message.createdBy = users[message.createdBy]; } - - // Attach the user profiles to the message objects - _.each(messages, message => { - if (users[message.createdBy]) { - message.createdBy = users[message.createdBy]; - } - }); - - return callback(err, messages, nextToken); }); - } - ); + + return callback(err, messages, nextToken); + }); + }); }); }); }; @@ -1912,18 +1778,17 @@ const _removeContentItemsFromFolder = function(folder, contentIds, callback) { */ const _augmentFolder = function(ctx, folder) { if (folder.previews && folder.previews.thumbnailUri) { - folder.previews.thumbnailUrl = ContentUtil.getSignedDownloadUrl( - ctx, - folder.previews.thumbnailUri - ); + folder.previews.thumbnailUrl = ContentUtil.getSignedDownloadUrl(ctx, folder.previews.thumbnailUri); } + if (folder.previews && folder.previews.wideUri) { folder.previews.wideUrl = ContentUtil.getSignedDownloadUrl(ctx, folder.previews.wideUri); } + return folder; }; -module.exports = { +export { createFolder, updateFolder, updateFolderContentVisibility, @@ -1945,5 +1810,5 @@ module.exports = { createMessage, deleteMessage, getMessages, - emitter: FoldersAPI + FoldersAPI as emitter }; diff --git a/packages/oae-folders/lib/authz.js b/packages/oae-folders/lib/authz.js index 4cea7786cb..6d392a5a50 100644 --- a/packages/oae-folders/lib/authz.js +++ b/packages/oae-folders/lib/authz.js @@ -13,14 +13,15 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const AuthzAPI = require('oae-authz'); -const AuthzPermissions = require('oae-authz/lib/permissions'); -const AuthzUtil = require('oae-authz/lib/util'); -const log = require('oae-logger').logger('folders-authz'); +import * as AuthzAPI from 'oae-authz'; +import * as AuthzPermissions from 'oae-authz/lib/permissions'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import { logger } from 'oae-logger'; +import * as FoldersDAO from './internal/dao'; -const FoldersDAO = require('./internal/dao'); +const log = logger('folders-authz'); /** * Determine if the user invoking the current request is allowed to add content items to a given @@ -101,8 +102,4 @@ const getContentInFolder = function(folder, callback) { }); }; -module.exports = { - getFoldersForContent, - canAddItemsToFolder, - getContentInFolder -}; +export { getFoldersForContent, canAddItemsToFolder, getContentInFolder }; diff --git a/packages/oae-folders/lib/constants.js b/packages/oae-folders/lib/constants.js index a7420aa7e4..b7e3f69066 100644 --- a/packages/oae-folders/lib/constants.js +++ b/packages/oae-folders/lib/constants.js @@ -59,6 +59,4 @@ FoldersConstants.search = { MAPPING_FOLDER_MESSAGE: 'folder_message' }; -module.exports = { - FoldersConstants -}; +export { FoldersConstants }; diff --git a/packages/oae-folders/lib/init.js b/packages/oae-folders/lib/init.js index 9ab6297bbe..73345769d8 100644 --- a/packages/oae-folders/lib/init.js +++ b/packages/oae-folders/lib/init.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const FoldersSearch = require('./search'); +import * as FoldersSearch from './search'; -module.exports = function(config, callback) { +export function init(config, callback) { // Register activity, library, previews and search functionality // eslint-disable-next-line no-unused-vars const activity = require('./activity'); @@ -27,4 +27,4 @@ module.exports = function(config, callback) { const invitations = require('./invitations'); return FoldersSearch.init(callback); -}; +} diff --git a/packages/oae-folders/lib/internal/contentLibrary.js b/packages/oae-folders/lib/internal/contentLibrary.js index 116fd06b9f..740bacbb37 100644 --- a/packages/oae-folders/lib/internal/contentLibrary.js +++ b/packages/oae-folders/lib/internal/contentLibrary.js @@ -14,12 +14,14 @@ */ /* eslint-disable unicorn/filename-case */ -const _ = require('underscore'); +import _ from 'underscore'; -const LibraryAPI = require('oae-library'); -const log = require('oae-logger').logger('oae-folders-contentlibrary'); +import * as LibraryAPI from 'oae-library'; +import { logger } from 'oae-logger'; -const { FoldersConstants } = require('../constants'); +import { FoldersConstants } from '../constants'; + +const log = logger('oae-folders-contentlibrary'); /** * Get the ids of the content items in the content library of a folder @@ -115,11 +117,7 @@ const remove = function(folder, contentItems, callback) { * @param {Object} callback.err An error that occurred, if any */ const purge = function(folder, callback) { - LibraryAPI.Index.purge( - FoldersConstants.library.CONTENT_LIBRARY_INDEX_NAME, - folder.groupId, - callback - ); + LibraryAPI.Index.purge(FoldersConstants.library.CONTENT_LIBRARY_INDEX_NAME, folder.groupId, callback); }; /** @@ -178,9 +176,4 @@ const _remove = function(folder, contentItems, callback) { LibraryAPI.Index.remove(FoldersConstants.library.CONTENT_LIBRARY_INDEX_NAME, entries, callback); }; -module.exports = { - list, - insert, - remove, - purge -}; +export { list, insert, remove, purge }; diff --git a/packages/oae-folders/lib/internal/dao.js b/packages/oae-folders/lib/internal/dao.js index 0ed22708cc..b07bb718cf 100644 --- a/packages/oae-folders/lib/internal/dao.js +++ b/packages/oae-folders/lib/internal/dao.js @@ -13,18 +13,18 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); -const ShortId = require('shortid'); +import util from 'util'; +import _ from 'underscore'; +import ShortId from 'shortid'; -const AuthzAPI = require('oae-authz'); -const AuthzUtil = require('oae-authz/lib/util'); -const Cassandra = require('oae-util/lib/cassandra'); -const ContentDAO = require('oae-content/lib/internal/dao'); -const PrincipalsUtil = require('oae-principals/lib/util'); -const TenantsAPI = require('oae-tenants'); +import * as AuthzAPI from 'oae-authz'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as ContentDAO from 'oae-content/lib/internal/dao'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; +import * as TenantsAPI from 'oae-tenants'; -const { Folder } = require('oae-folders/lib/model'); +import { Folder } from 'oae-folders/lib/model'; /** * Create a folder @@ -54,12 +54,7 @@ const createFolder = function(createdBy, displayName, description, visibility, c }; // Create the queries to insert both the folder and the record that indexes it with its surrogate group id - const insertGroupIdIndexQuery = Cassandra.constructUpsertCQL( - 'FoldersGroupId', - 'groupId', - groupId, - { folderId } - ); + const insertGroupIdIndexQuery = Cassandra.constructUpsertCQL('FoldersGroupId', 'groupId', groupId, { folderId }); const insertFolderQuery = Cassandra.constructUpsertCQL('Folders', 'id', folderId, storageHash); // Insert the surrogate group id index entry @@ -119,30 +114,26 @@ const getFoldersByGroupIds = function(groupIds, callback) { return callback(null, []); } - Cassandra.runQuery( - 'SELECT * FROM "FoldersGroupId" WHERE "groupId" IN ?', - [groupIds], - (err, rows) => { - if (err) { - return callback(err); - } - - // Assemble the folder ids, ensuring the original ordering is maintained - const folderIdsByGroupIds = _.chain(rows) - .map(Cassandra.rowToHash) - .indexBy('groupId') - .value(); - const folderIds = _.chain(groupIds) - .map(groupId => { - return folderIdsByGroupIds[groupId]; - }) - .compact() - .pluck('folderId') - .value(); - - return getFoldersByIds(folderIds, callback); + Cassandra.runQuery('SELECT * FROM "FoldersGroupId" WHERE "groupId" IN ?', [groupIds], (err, rows) => { + if (err) { + return callback(err); } - ); + + // Assemble the folder ids, ensuring the original ordering is maintained + const folderIdsByGroupIds = _.chain(rows) + .map(Cassandra.rowToHash) + .indexBy('groupId') + .value(); + const folderIds = _.chain(groupIds) + .map(groupId => { + return folderIdsByGroupIds[groupId]; + }) + .compact() + .pluck('folderId') + .value(); + + return getFoldersByIds(folderIds, callback); + }); }; /** @@ -158,6 +149,7 @@ const getFolder = function(folderId, callback) { if (err) { return callback(err); } + if (_.isEmpty(folders)) { return callback({ code: 404, @@ -222,27 +214,21 @@ const getContentItems = function(folderGroupId, opts, callback) { // Query all the content ids ('c') to which the folder is directly associated in this batch of // paged resources. Since the group can be a member of both user groups and folder groups, we // filter down to just the folder groups for folder libraries - AuthzAPI.getRolesForPrincipalAndResourceType( - folderGroupId, - 'c', - opts.start, - opts.limit, - (err, roles, nextToken) => { + AuthzAPI.getRolesForPrincipalAndResourceType(folderGroupId, 'c', opts.start, opts.limit, (err, roles, nextToken) => { + if (err) { + return callback(err); + } + + // Get all the content items that we queried by id + const ids = _.pluck(roles, 'id'); + ContentDAO.Content.getMultipleContentItems(ids, opts.fields, (err, contentItems) => { if (err) { return callback(err); } - // Get all the content items that we queried by id - const ids = _.pluck(roles, 'id'); - ContentDAO.Content.getMultipleContentItems(ids, opts.fields, (err, contentItems) => { - if (err) { - return callback(err); - } - - return callback(null, contentItems, nextToken); - }); - } - ); + return callback(null, contentItems, nextToken); + }); + }); }; /** @@ -290,10 +276,10 @@ const iterateAll = function(properties, batchSize, onEach, callback) { } /*! - * Handles each batch from the cassandra iterateAll method - * - * @see Cassandra#iterateAll - */ + * Handles each batch from the cassandra iterateAll method + * + * @see Cassandra#iterateAll + */ const _iterateAllOnEach = function(rows, done) { // Convert the rows to a hash and delegate action to the caller onEach method return onEach(_.map(rows, Cassandra.rowToHash), done); @@ -340,6 +326,7 @@ const _rowToFolder = function(row) { } catch (error) { storageHash.previews = {}; } + return _storageHashToFolder(storageHash.id, storageHash); }; @@ -377,7 +364,7 @@ const _createFolderId = function(tenantAlias) { return AuthzUtil.toId('f', tenantAlias, ShortId.generate()); }; -module.exports = { +export { setPreviews, iterateAll, getContentItems, diff --git a/packages/oae-folders/lib/internal/foldersLibrary.js b/packages/oae-folders/lib/internal/foldersLibrary.js index 5209896456..c59d8c9aef 100644 --- a/packages/oae-folders/lib/internal/foldersLibrary.js +++ b/packages/oae-folders/lib/internal/foldersLibrary.js @@ -14,15 +14,17 @@ */ /* eslint-disable unicorn/filename-case */ -const _ = require('underscore'); -const { Long } = require('cassandra-driver').types; +import _ from 'underscore' +import { types } from "cassandra-driver"; -const LibraryAPI = require('oae-library'); -const OaeUtil = require('oae-util/lib/util'); -const log = require('oae-logger').logger('oae-folders-contentLibrary'); +import * as LibraryAPI from 'oae-library' +import * as OaeUtil from 'oae-util/lib/util' +import { logger } from "oae-logger"; -const { FoldersConstants } = require('../constants'); -const FoldersDAO = require('./dao'); +import { FoldersConstants } from '../constants' +import * as FoldersDAO from './dao' +const { Long } = types; +const log = logger('oae-folders-contentLibrary');; /** * Get the ids of the folders in the folders library of a specified user or group @@ -138,37 +140,31 @@ const update = function(principalIds, folder, oldLastModified, callback) { // If the caller specified we should "touch" the folder, we simply update its last modified // timestamp before updating the library indices - OaeUtil.invokeIfNecessary( - touchFolder, - FoldersDAO.updateFolder, - folder, - {}, - (err, updatedFolder) => { + OaeUtil.invokeIfNecessary(touchFolder, FoldersDAO.updateFolder, folder, {}, (err, updatedFolder) => { + if (err) { + return callback(err); + } + + folder = updatedFolder || folder; + + const entries = _.map(principalIds, principalId => { + return { + id: principalId, + oldRank: oldLastModified, + newRank: folder.lastModified, + resource: folder + }; + }); + + // Update the library entries for the provided principal ids + LibraryAPI.Index.update(FoldersConstants.library.FOLDERS_LIBRARY_INDEX_NAME, entries, err => { if (err) { return callback(err); } - folder = updatedFolder || folder; - - const entries = _.map(principalIds, principalId => { - return { - id: principalId, - oldRank: oldLastModified, - newRank: folder.lastModified, - resource: folder - }; - }); - - // Update the library entries for the provided principal ids - LibraryAPI.Index.update(FoldersConstants.library.FOLDERS_LIBRARY_INDEX_NAME, entries, err => { - if (err) { - return callback(err); - } - - return callback(null, folder); - }); - } - ); + return callback(null, folder); + }); + }); }; /** @@ -210,9 +206,4 @@ const remove = function(principalIds, folder, callback) { LibraryAPI.Index.remove(FoldersConstants.library.FOLDERS_LIBRARY_INDEX_NAME, entries, callback); }; -module.exports = { - list, - insert, - update, - remove -}; +export { list, insert, update, remove }; diff --git a/packages/oae-folders/lib/invitations.js b/packages/oae-folders/lib/invitations.js index 04e329c6bf..873edde1fd 100644 --- a/packages/oae-folders/lib/invitations.js +++ b/packages/oae-folders/lib/invitations.js @@ -13,20 +13,22 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const AuthzInvitationsDAO = require('oae-authz/lib/invitations/dao'); -const AuthzUtil = require('oae-authz/lib/util'); -const { Context } = require('oae-context'); -const { Invitation } = require('oae-authz/lib/invitations/model'); -const ResourceActions = require('oae-resource/lib/actions'); -const { ResourceConstants } = require('oae-resource/lib/constants'); +import { logger } from 'oae-logger'; -const FoldersAPI = require('oae-folders'); -const { FoldersConstants } = require('oae-folders/lib/constants'); -const FoldersDAO = require('oae-folders/lib/internal/dao'); +import * as AuthzInvitationsDAO from 'oae-authz/lib/invitations/dao'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ResourceActions from 'oae-resource/lib/actions'; +import * as FoldersAPI from 'oae-folders'; +import * as FoldersDAO from 'oae-folders/lib/internal/dao'; -const log = require('oae-logger').logger('oae-folders-invitations'); +import { Context } from 'oae-context'; +import { Invitation } from 'oae-authz/lib/invitations/model'; +import { ResourceConstants } from 'oae-resource/lib/constants'; +import { FoldersConstants } from 'oae-folders/lib/constants'; + +const log = logger('oae-folders-invitations'); /*! * When an invitation is accepted, pass on the events to update folder members and then feed back @@ -55,6 +57,7 @@ ResourceActions.emitter.when( ); return callback(); } + if (_.isEmpty(folders)) { return callback(); } @@ -87,24 +90,21 @@ ResourceActions.emitter.when( /*! * When a folder is deleted, we delete all invitations associated to it */ -FoldersAPI.emitter.when( - FoldersConstants.events.DELETED_FOLDER, - (ctx, folder, memberIds, callback) => { - AuthzInvitationsDAO.deleteInvitationsByResourceId(folder.id, err => { - if (err) { - log().warn( - { - err, - folderId: folder.id - }, - 'An error occurred while removing invitations after a folder was deleted' - ); - } +FoldersAPI.emitter.when(FoldersConstants.events.DELETED_FOLDER, (ctx, folder, memberIds, callback) => { + AuthzInvitationsDAO.deleteInvitationsByResourceId(folder.id, err => { + if (err) { + log().warn( + { + err, + folderId: folder.id + }, + 'An error occurred while removing invitations after a folder was deleted' + ); + } - return callback(); - }); - } -); + return callback(); + }); +}); /** * Determine if the given id is a folder id diff --git a/packages/oae-folders/lib/library.js b/packages/oae-folders/lib/library.js index 0dad058a20..133e29a672 100644 --- a/packages/oae-folders/lib/library.js +++ b/packages/oae-folders/lib/library.js @@ -13,23 +13,27 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); - -const AuthzAPI = require('oae-authz'); -const AuthzUtil = require('oae-authz/lib/util'); -const ContentAPI = require('oae-content'); -const { ContentConstants } = require('oae-content/lib/constants'); -const Counter = require('oae-util/lib/counter'); -const LibraryAPI = require('oae-library'); -const OaeUtil = require('oae-util/lib/util'); -const log = require('oae-logger').logger('oae-folders-library'); - -const FoldersAPI = require('oae-folders'); -const FoldersAuthz = require('oae-folders/lib/authz'); -const { FoldersConstants } = require('oae-folders/lib/constants'); -const FoldersContentLibrary = require('oae-folders/lib/internal/contentLibrary'); -const FoldersDAO = require('oae-folders/lib/internal/dao'); -const FoldersFoldersLibrary = require('oae-folders/lib/internal/foldersLibrary'); +import _ from 'underscore'; + +import * as AuthzAPI from 'oae-authz'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ContentAPI from 'oae-content'; +import Counter from 'oae-util/lib/counter'; +import * as LibraryAPI from 'oae-library'; +import * as OaeUtil from 'oae-util/lib/util'; + +import { logger } from 'oae-logger'; + +import * as FoldersAPI from 'oae-folders'; +import * as FoldersAuthz from 'oae-folders/lib/authz'; +import * as FoldersContentLibrary from 'oae-folders/lib/internal/contentLibrary'; +import * as FoldersDAO from 'oae-folders/lib/internal/dao'; +import * as FoldersFoldersLibrary from 'oae-folders/lib/internal/foldersLibrary'; + +import { FoldersConstants } from 'oae-folders/lib/constants'; +import { ContentConstants } from 'oae-content/lib/constants'; + +const log = logger('oae-folders-library'); // When updating folders in a principal folders library, update at most once every hour to // avoid thrashing the libraries with updates and causing duplicates @@ -58,37 +62,31 @@ LibraryAPI.Index.registerLibraryIndex(FoldersConstants.library.FOLDERS_LIBRARY_I // Query all the group ids ('g') to which the principal is directly associated in this // batch of paged resources. Since the group can be a member of both user groups and // folder groups, we filter down to just the folder groups for folder libraries - AuthzAPI.getRolesForPrincipalAndResourceType( - libraryId, - 'g', - start, - limit, - (err, roles, nextToken) => { + AuthzAPI.getRolesForPrincipalAndResourceType(libraryId, 'g', start, limit, (err, roles, nextToken) => { + if (err) { + return callback(err); + } + + // We just need the ids, not the roles + const ids = _.pluck(roles, 'id'); + FoldersDAO.getFoldersByGroupIds(ids, (err, folders) => { if (err) { return callback(err); } - // We just need the ids, not the roles - const ids = _.pluck(roles, 'id'); - FoldersDAO.getFoldersByGroupIds(ids, (err, folders) => { - if (err) { - return callback(err); - } - - // Remove empty items, which indicates they mapped to user groups and not folder - // groups - folders = _.compact(folders); - - // Convert all the folders into the light-weight library items that describe how - // they are placed in a library index - const resources = _.map(folders, folder => { - return { rank: folder.lastModified, resource: folder }; - }); + // Remove empty items, which indicates they mapped to user groups and not folder + // groups + folders = _.compact(folders); - return callback(null, resources, nextToken); + // Convert all the folders into the light-weight library items that describe how + // they are placed in a library index + const resources = _.map(folders, folder => { + return { rank: folder.lastModified, resource: folder }; }); - } - ); + + return callback(null, resources, nextToken); + }); + }); } }); @@ -146,26 +144,23 @@ LibraryAPI.Search.registerLibrarySearch('folder-library', ['folder']); /*! * When a folder is created, insert it into all folder libraries that it becomes a part of */ -FoldersAPI.emitter.when( - FoldersConstants.events.CREATED_FOLDER, - (ctx, folder, memberChangeInfo, callback) => { - const addedMemberIds = _.pluck(memberChangeInfo.members.added, 'id'); - FoldersFoldersLibrary.insert(addedMemberIds, folder, err => { - if (err) { - log().warn( - { - err, - folderId: folder.id, - memberIds: addedMemberIds - }, - 'An error occurred while inserting a folder into folder libraries after create' - ); - } +FoldersAPI.emitter.when(FoldersConstants.events.CREATED_FOLDER, (ctx, folder, memberChangeInfo, callback) => { + const addedMemberIds = _.pluck(memberChangeInfo.members.added, 'id'); + FoldersFoldersLibrary.insert(addedMemberIds, folder, err => { + if (err) { + log().warn( + { + err, + folderId: folder.id, + memberIds: addedMemberIds + }, + 'An error occurred while inserting a folder into folder libraries after create' + ); + } - return callback(); - }); - } -); + return callback(); + }); +}); /*! * When a folder is updated, we need to update the folder libraries it's in @@ -177,19 +172,13 @@ FoldersAPI.emitter.on(FoldersConstants.events.UPDATED_FOLDER, (ctx, updatedFolde _getAllMemberIds(updatedFolder.groupId, (err, memberIds) => { if (err) { purgeCounter.decr(); - return log().error( - { err, updatedFolder }, - 'An error occurred while retrieving the members for a folder' - ); + return log().error({ err, updatedFolder }, 'An error occurred while retrieving the members for a folder'); } FoldersFoldersLibrary.update(memberIds, updatedFolder, oldFolder.lastModified, err => { if (err) { purgeCounter.decr(); - return log().error( - { err, updatedFolder }, - 'Could not update the folder libraries for a set of users' - ); + return log().error({ err, updatedFolder }, 'Could not update the folder libraries for a set of users'); } // At this point the async operation is over @@ -202,44 +191,41 @@ FoldersAPI.emitter.on(FoldersConstants.events.UPDATED_FOLDER, (ctx, updatedFolde * When a folder gets deleted we remove it as an authz member of all the content items * it contained. Eventually we also purge all its content libraries */ -FoldersAPI.emitter.when( - FoldersConstants.events.DELETED_FOLDER, - (ctx, folder, removedMemberIds, callback) => { - // Keep track of the async operation - purgeCounter.incr(); +FoldersAPI.emitter.when(FoldersConstants.events.DELETED_FOLDER, (ctx, folder, removedMemberIds, callback) => { + // Keep track of the async operation + purgeCounter.incr(); + + // Purge the content library as it's no longer needed + FoldersContentLibrary.purge(folder, err => { + if (err) { + log().error( + { + err, + folderId: folder.id, + folderGroupId: folder.groupId + }, + 'Unable to purge a folder content library' + ); + } - // Purge the content library as it's no longer needed - FoldersContentLibrary.purge(folder, err => { + FoldersFoldersLibrary.remove(removedMemberIds, folder, err => { if (err) { log().error( { err, folderId: folder.id, - folderGroupId: folder.groupId + folderGroupId: folder.groupId, + removedMemberIds }, - 'Unable to purge a folder content library' + 'An error occurred while purging a folder content library' ); } - FoldersFoldersLibrary.remove(removedMemberIds, folder, err => { - if (err) { - log().error( - { - err, - folderId: folder.id, - folderGroupId: folder.groupId, - removedMemberIds - }, - 'An error occurred while purging a folder content library' - ); - } - - purgeCounter.decr(); - return callback(); - }); + purgeCounter.decr(); + return callback(); }); - } -); + }); +}); /** * When a folder members are updated, pass the required updates to its members library as well @@ -251,7 +237,7 @@ FoldersAPI.emitter.when( purgeCounter.incr(); const addedMemberIds = _.pluck(memberChangeInfo.members.added, 'id'); - // const updatedMemberIds = _.pluck(memberChangeInfo.members.updated, 'id'); + // Const updatedMemberIds = _.pluck(memberChangeInfo.members.updated, 'id'); const removedMemberIds = _.pluck(memberChangeInfo.members.removed, 'id'); // Insert the folder into the libraries of new members @@ -367,26 +353,33 @@ FoldersAPI.emitter.on(FoldersConstants.events.CREATED_COMMENT, (ctx, message, fo * 3. We need to bump the last updated items in a library to the top. By re-inserting * the content item with its latest `lastModified` timestamp this will be achieved */ -ContentAPI.emitter.on( - ContentConstants.events.UPDATED_CONTENT, - (ctx, newContentObj, oldContentObj) => { - purgeCounter.incr(); +ContentAPI.emitter.on(ContentConstants.events.UPDATED_CONTENT, (ctx, newContentObj, oldContentObj) => { + purgeCounter.incr(); - // Purge all the libraries this content item was in - FoldersAuthz.getFoldersForContent(newContentObj.id, (err, folders) => { - if (err) { - // The error is logged further down the chain, there isn't much more that we can do - purgeCounter.decr(); - return; - } + // Purge all the libraries this content item was in + FoldersAuthz.getFoldersForContent(newContentObj.id, (err, folders) => { + if (err) { + // The error is logged further down the chain, there isn't much more that we can do + purgeCounter.decr(); + return; + } + + // Remove and insert the content item from the folder-content library so its placed + // in the correct visibility bucket + _.each(folders, folder => { + purgeCounter.incr(); - // Remove and insert the content item from the folder-content library so its placed - // in the correct visibility bucket - _.each(folders, folder => { - purgeCounter.incr(); + // Remove the content item from the old visibility bucket + FoldersContentLibrary.remove(folder, [oldContentObj], err => { + if (err) { + log().error( + { err, folder: folder.folderId, contentId: oldContentObj.id }, + "Unable to update a folder's content library" + ); + } - // Remove the content item from the old visibility bucket - FoldersContentLibrary.remove(folder, [oldContentObj], err => { + // Insert the content item in the proper visibility bucket + FoldersContentLibrary.insert(folder, [newContentObj], err => { if (err) { log().error( { err, folder: folder.folderId, contentId: oldContentObj.id }, @@ -394,24 +387,14 @@ ContentAPI.emitter.on( ); } - // Insert the content item in the proper visibility bucket - FoldersContentLibrary.insert(folder, [newContentObj], err => { - if (err) { - log().error( - { err, folder: folder.folderId, contentId: oldContentObj.id }, - "Unable to update a folder's content library" - ); - } - - purgeCounter.decr(); - }); + purgeCounter.decr(); }); }); - - purgeCounter.decr(); }); - } -); + + purgeCounter.decr(); + }); +}); /*! * When a content item is removed, we need to remove it from the folders it's in @@ -470,12 +453,7 @@ const _getAllMemberIds = function(groupId, callback) { * @api private */ const _testLibraryUpdateThreshold = function(folder) { - return ( - !folder.lastModified || - Date.now() - folder.lastModified > LIBRARY_UPDATE_THRESHOLD_SECONDS * 1000 - ); + return !folder.lastModified || Date.now() - folder.lastModified > LIBRARY_UPDATE_THRESHOLD_SECONDS * 1000; }; -module.exports = { - whenAllPurged -}; +export { whenAllPurged }; diff --git a/packages/oae-folders/lib/migration.js b/packages/oae-folders/lib/migration.js index da59679628..b9806e0915 100644 --- a/packages/oae-folders/lib/migration.js +++ b/packages/oae-folders/lib/migration.js @@ -1,4 +1,4 @@ -const Cassandra = require('oae-util/lib/cassandra'); +import * as Cassandra from 'oae-util/lib/cassandra'; /** * Ensure that the all of the folders schemas are created. If they already exist, this method will not do anything @@ -18,4 +18,4 @@ const ensureSchema = function(callback) { ); }; -module.exports = { ensureSchema }; +export { ensureSchema }; diff --git a/packages/oae-folders/lib/model.js b/packages/oae-folders/lib/model.js index 8b208ff143..81f874b1ce 100644 --- a/packages/oae-folders/lib/model.js +++ b/packages/oae-folders/lib/model.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const util = require('util'); +import util from 'util'; -const AuthzUtil = require('oae-authz/lib/util'); +import * as AuthzUtil from 'oae-authz/lib/util'; /** * A model object that represents a folder @@ -60,4 +60,4 @@ const Folder = function( return that; }; -module.exports = { Folder }; +export { Folder }; diff --git a/packages/oae-folders/lib/previews.js b/packages/oae-folders/lib/previews.js index 2412e0da33..779e082e50 100644 --- a/packages/oae-folders/lib/previews.js +++ b/packages/oae-folders/lib/previews.js @@ -13,21 +13,23 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); - -const AuthzUtil = require('oae-authz/lib/util'); -const ContentAPI = require('oae-content'); -const { ContentConstants } = require('oae-content/lib/constants'); -const Counter = require('oae-util/lib/counter'); -const MQTestUtil = require('oae-util/lib/test/mq-util'); -const PreviewProcessorAPI = require('oae-preview-processor'); -const PreviewConstants = require('oae-preview-processor/lib/constants'); - -const FoldersAPI = require('oae-folders'); -const FoldersAuthz = require('oae-folders/lib/authz'); -const { FoldersConstants } = require('oae-folders/lib/constants'); -const FoldersDAO = require('oae-folders/lib/internal/dao'); -const log = require('oae-logger').logger('oae-folders-previews'); +import _ from 'underscore'; + +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ContentAPI from 'oae-content'; +import Counter from 'oae-util/lib/counter'; +import * as MQTestUtil from 'oae-util/lib/test/mq-util'; +import * as PreviewProcessorAPI from 'oae-preview-processor'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; + +import * as FoldersAPI from 'oae-folders'; +import * as FoldersAuthz from 'oae-folders/lib/authz'; +import * as FoldersDAO from 'oae-folders/lib/internal/dao'; +import { ContentConstants } from 'oae-content/lib/constants'; +import { FoldersConstants } from 'oae-folders/lib/constants'; +import { logger } from 'oae-logger'; + +const log = logger('oae-folders-previews'); const previewCounter = new Counter(); @@ -71,12 +73,9 @@ const _handleContentChange = function(ctx, folder, contentItems) { /*! * If a content item gets added to a folder we need to generate previews for the folder */ -FoldersAPI.emitter.on( - FoldersConstants.events.ADDED_CONTENT_ITEMS, - (ctx, actionContext, folder, contentItems) => { - return _handleContentChange(ctx, folder, contentItems); - } -); +FoldersAPI.emitter.on(FoldersConstants.events.ADDED_CONTENT_ITEMS, (ctx, actionContext, folder, contentItems) => { + return _handleContentChange(ctx, folder, contentItems); +}); /*! * If a content item gets removed from a folder, we need to regenerate the previews for the folder @@ -127,14 +126,11 @@ ContentAPI.emitter.on(ContentConstants.events.DELETED_CONTENT, (ctx, contentObj, /*! * If a content item's visibility setting changes we need to regenerate the preview items for those folders that contain the content item */ -ContentAPI.emitter.on( - ContentConstants.events.UPDATED_CONTENT, - (ctx, newContentObj, oldContentObj) => { - if (newContentObj.visibility !== oldContentObj.visibility) { - _reprocessFoldersThatContainContent(newContentObj.id); - } +ContentAPI.emitter.on(ContentConstants.events.UPDATED_CONTENT, (ctx, newContentObj, oldContentObj) => { + if (newContentObj.visibility !== oldContentObj.visibility) { + _reprocessFoldersThatContainContent(newContentObj.id); } -); +}); /** * Reprocess the folders that contain a given content item @@ -162,6 +158,4 @@ const _reprocessFoldersThatContainContent = function(contentId) { }); }; -module.exports = { - whenPreviewsComplete -}; +export { whenPreviewsComplete }; diff --git a/packages/oae-folders/lib/rest.js b/packages/oae-folders/lib/rest.js index 5f7ca23937..0cc2ff716f 100644 --- a/packages/oae-folders/lib/rest.js +++ b/packages/oae-folders/lib/rest.js @@ -13,13 +13,13 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const { AuthzConstants } = require('oae-authz/lib/constants'); -const OAE = require('oae-util/lib/oae'); -const OaeUtil = require('oae-util/lib/util'); +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as OAE from 'oae-util/lib/oae'; +import * as OaeUtil from 'oae-util/lib/util'; -const FoldersAPI = require('oae-folders'); +import * as FoldersAPI from 'oae-folders'; /** * @REST postFolder @@ -168,19 +168,14 @@ OAE.tenantRouter.on('post', '/api/folder/:folderId', (req, res) => { * @HttpResponse 401 You're not allowed to update this folder */ OAE.tenantRouter.on('post', '/api/folder/:folderId/contentvisibility', (req, res) => { - FoldersAPI.updateFolderContentVisibility( - req.ctx, - req.params.folderId, - req.body.visibility, - (err, failedContent) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - const data = { failedContent }; - return res.status(200).send(data); + FoldersAPI.updateFolderContentVisibility(req.ctx, req.params.folderId, req.body.visibility, (err, failedContent) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + const data = { failedContent }; + return res.status(200).send(data); + }); }); /** @@ -288,19 +283,13 @@ OAE.tenantRouter.on('post', '/api/folder/:folderId/members', (req, res) => { */ OAE.tenantRouter.on('get', '/api/folder/:folderId/members', (req, res) => { const limit = OaeUtil.getNumberParam(req.query.limit, 10, 1, 25); - FoldersAPI.getFolderMembers( - req.ctx, - req.params.folderId, - req.query.start, - limit, - (err, members, nextToken) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(200).send({ results: members, nextToken }); + FoldersAPI.getFolderMembers(req.ctx, req.params.folderId, req.query.start, limit, (err, members, nextToken) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + return res.status(200).send({ results: members, nextToken }); + }); }); /** @@ -374,19 +363,13 @@ OAE.tenantRouter.on('post', '/api/folder/:folderId/invitations/:email/resend', ( */ OAE.tenantRouter.on('get', '/api/folder/library/:principalId', (req, res) => { const limit = OaeUtil.getNumberParam(req.query.limit, 12, 1, 25); - FoldersAPI.getFoldersLibrary( - req.ctx, - req.params.principalId, - req.query.start, - limit, - (err, results, nextToken) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(200).send({ results, nextToken }); + FoldersAPI.getFoldersLibrary(req.ctx, req.params.principalId, req.query.start, limit, (err, results, nextToken) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + return res.status(200).send({ results, nextToken }); + }); }); /** @@ -526,19 +509,13 @@ OAE.tenantRouter.on('get', '/api/folder/:folderId/library', (req, res) => { */ OAE.tenantRouter.on('get', '/api/folder/:folderId/messages', (req, res) => { const limit = OaeUtil.getNumberParam(req.query.limit, 10, 1, 25); - FoldersAPI.getMessages( - req.ctx, - req.params.folderId, - req.query.start, - limit, - (err, messages, nextToken) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(200).send({ results: messages, nextToken }); + FoldersAPI.getMessages(req.ctx, req.params.folderId, req.query.start, limit, (err, messages, nextToken) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + return res.status(200).send({ results: messages, nextToken }); + }); }); /** @@ -564,19 +541,13 @@ OAE.tenantRouter.on('get', '/api/folder/:folderId/messages', (req, res) => { * @HttpResponse 404 Could not find the specified folder */ OAE.tenantRouter.on('post', '/api/folder/:folderId/messages', (req, res) => { - FoldersAPI.createMessage( - req.ctx, - req.params.folderId, - req.body.body, - req.body.replyTo, - (err, message) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(200).send(message); + FoldersAPI.createMessage(req.ctx, req.params.folderId, req.body.body, req.body.replyTo, (err, message) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + return res.status(200).send(message); + }); }); /** diff --git a/packages/oae-folders/lib/search.js b/packages/oae-folders/lib/search.js index cecae9d94e..a79dc1c83a 100644 --- a/packages/oae-folders/lib/search.js +++ b/packages/oae-folders/lib/search.js @@ -15,20 +15,22 @@ /* eslint-disable no-unused-vars */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const AuthzSearch = require('oae-authz/lib/search'); -const AuthzUtil = require('oae-authz/lib/util'); -const ContentUtil = require('oae-content/lib/internal/util'); -const log = require('oae-logger').logger('folders-search'); -const MessageBoxSearch = require('oae-messagebox/lib/search'); -const SearchAPI = require('oae-search'); -const TenantsAPI = require('oae-tenants'); +import * as AuthzSearch from 'oae-authz/lib/search'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ContentUtil from 'oae-content/lib/internal/util'; +import { logger } from 'oae-logger'; +import * as MessageBoxSearch from 'oae-messagebox/lib/search'; +import * as SearchAPI from 'oae-search'; +import * as TenantsAPI from 'oae-tenants'; -const FoldersAPI = require('oae-folders'); -const { FoldersConstants } = require('oae-folders/lib/constants'); -const FoldersDAO = require('oae-folders/lib/internal/dao'); +import * as FoldersAPI from 'oae-folders'; +import { FoldersConstants } from 'oae-folders/lib/constants'; +import * as FoldersDAO from 'oae-folders/lib/internal/dao'; + +const log = logger('folders-search'); /** * Initializes the child search documents for the folders module @@ -98,26 +100,23 @@ FoldersAPI.emitter.on(FoldersConstants.events.UPDATED_FOLDER_PREVIEWS, folder => * When the members of a folder are updated, fire a task to update the memberships search * documents of those whose roles have changed */ -FoldersAPI.emitter.on( - FoldersConstants.events.UPDATED_FOLDER_MEMBERS, - (ctx, folder, memberChangeInfo, opts) => { - // Update the members document for this folder - SearchAPI.postIndexTask('folder', [{ id: folder.groupId, folderId: folder.id }], { - children: { - // eslint-disable-next-line camelcase - resource_members: true - } - }); +FoldersAPI.emitter.on(FoldersConstants.events.UPDATED_FOLDER_MEMBERS, (ctx, folder, memberChangeInfo, opts) => { + // Update the members document for this folder + SearchAPI.postIndexTask('folder', [{ id: folder.groupId, folderId: folder.id }], { + children: { + // eslint-disable-next-line camelcase + resource_members: true + } + }); - // Update each of the updated members their membership documents - const principalIds = _.chain(memberChangeInfo.members.added) - .union(memberChangeInfo.members.updated) - .union(memberChangeInfo.members.removed) - .pluck('id') - .value(); - AuthzSearch.fireMembershipUpdateTasks(principalIds); - } -); + // Update each of the updated members their membership documents + const principalIds = _.chain(memberChangeInfo.members.added) + .union(memberChangeInfo.members.updated) + .union(memberChangeInfo.members.removed) + .pluck('id') + .value(); + AuthzSearch.fireMembershipUpdateTasks(principalIds); +}); /** * Index the resource members for a set of content items @@ -144,12 +143,9 @@ const _indexContentResourceMembers = function(ctx, folder, contentItems) { * When content items are added to a folder, fire a task to update the members search document * of the content items that were added */ -FoldersAPI.emitter.on( - FoldersConstants.events.ADDED_CONTENT_ITEMS, - (ctx, actionContext, folder, contentItems) => { - return _indexContentResourceMembers(ctx, folder, contentItems); - } -); +FoldersAPI.emitter.on(FoldersConstants.events.ADDED_CONTENT_ITEMS, (ctx, actionContext, folder, contentItems) => { + return _indexContentResourceMembers(ctx, folder, contentItems); +}); /*! * When content items are removed from a folder, fire a task to update the members search document @@ -176,16 +172,13 @@ FoldersAPI.emitter.on(FoldersConstants.events.CREATED_COMMENT, (ctx, message, fo /*! * When a folder message is deleted, we must delete the child message document */ -FoldersAPI.emitter.on( - FoldersConstants.events.DELETED_COMMENT, - (ctx, message, folder, deleteType) => { - return MessageBoxSearch.deleteMessageSearchDocument( - FoldersConstants.search.MAPPING_FOLDER_MESSAGE, - folder.groupId, - message - ); - } -); +FoldersAPI.emitter.on(FoldersConstants.events.DELETED_COMMENT, (ctx, message, folder, deleteType) => { + return MessageBoxSearch.deleteMessageSearchDocument( + FoldersConstants.search.MAPPING_FOLDER_MESSAGE, + folder.groupId, + message + ); +}); /// ///////////////////// // DOCUMENT PRODUCERS // @@ -335,11 +328,7 @@ const _transformFolderDocuments = function(ctx, docs, callback) { // Add the full tenant object and profile path _.extend(result, { tenant: TenantsAPI.getTenant(result.tenantAlias).compact(), - profilePath: util.format( - '/folder/%s/%s', - result.tenantAlias, - AuthzUtil.getResourceFromId(result.id).resourceId - ) + profilePath: util.format('/folder/%s/%s', result.tenantAlias, AuthzUtil.getResourceFromId(result.id).resourceId) }); // If applicable, sign the thumbnailUrl so the current user can access it @@ -366,11 +355,11 @@ SearchAPI.registerSearchDocumentTransformer('folder', _transformFolderDocuments) */ SearchAPI.registerReindexAllHandler('folder', callback => { /*! - * Handles each iteration of the FoldersDAO iterate all method, firing tasks for all folders to - * be reindexed. - * - * @see FoldersDAO#iterateAll - */ + * Handles each iteration of the FoldersDAO iterate all method, firing tasks for all folders to + * be reindexed. + * + * @see FoldersDAO#iterateAll + */ const _onEach = function(folderRows, done) { // Aggregate folder reindexing task resources const folderResources = _.map(folderRows, row => { @@ -392,6 +381,4 @@ SearchAPI.registerReindexAllHandler('folder', callback => { FoldersDAO.iterateAll(['id', 'groupId'], 100, _onEach, callback); }); -module.exports = { - init -}; +export { init }; diff --git a/packages/oae-folders/lib/test/util.js b/packages/oae-folders/lib/test/util.js index e8927157f5..e3d0b48428 100644 --- a/packages/oae-folders/lib/test/util.js +++ b/packages/oae-folders/lib/test/util.js @@ -14,27 +14,27 @@ */ /* eslint-disable no-unused-vars */ -const assert = require('assert'); -const util = require('util'); -const _ = require('underscore'); -const shortid = require('shortid'); - -const AuthzTestUtil = require('oae-authz/lib/test/util'); -const AuthzUtil = require('oae-authz/lib/util'); -const ContentTestUtil = require('oae-content/lib/test/util'); -const { Context } = require('oae-context'); -const LibraryAPI = require('oae-library'); -const LibraryTestUtil = require('oae-library/lib/test/util'); -const MQTestUtil = require('oae-util/lib/test/mq-util'); -const PreviewConstants = require('oae-preview-processor/lib/constants'); -const PrincipalsAPI = require('oae-principals'); -const RestAPI = require('oae-rest'); -const SearchTestUtil = require('oae-search/lib/test/util'); -const { User } = require('oae-principals/lib/model'); - -const FoldersLibrary = require('oae-folders/lib/library'); -const FoldersDAO = require('../internal/dao'); -const { FoldersConstants } = require('../constants'); +import assert from 'assert'; +import util from 'util'; +import _ from 'underscore'; +import shortid from 'shortid'; + +import * as AuthzTestUtil from 'oae-authz/lib/test/util'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ContentTestUtil from 'oae-content/lib/test/util'; +import * as LibraryAPI from 'oae-library'; +import * as LibraryTestUtil from 'oae-library/lib/test/util'; +import * as MQTestUtil from 'oae-util/lib/test/mq-util'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; +import * as PrincipalsAPI from 'oae-principals'; +import * as RestAPI from 'oae-rest'; +import * as SearchTestUtil from 'oae-search/lib/test/util'; +import * as FoldersLibrary from 'oae-folders/lib/library'; + +import { Context } from 'oae-context'; +import { User } from 'oae-principals/lib/model'; +import * as FoldersDAO from '../internal/dao'; +import { FoldersConstants } from '../constants'; /** * Generate a number of folders for use in testing @@ -81,13 +81,7 @@ const generateTestFolders = function(restContext, numFolders, callback, _folders * @param {Folder} callback.folder... All folders that were generated as separate callback parameters * @throws {AssertionError} Thrown if an error occurred generating the folders */ -const generateTestFoldersWithVisibility = function( - restContext, - numFolders, - visibility, - callback, - _folders -) { +const generateTestFoldersWithVisibility = function(restContext, numFolders, visibility, callback, _folders) { _folders = _folders || []; if (numFolders === 0) { LibraryAPI.Index.whenUpdatesComplete(() => { @@ -108,13 +102,7 @@ const generateTestFoldersWithVisibility = function( (err, createdFolder) => { assert.ok(!err); _folders.push(createdFolder); - return generateTestFoldersWithVisibility( - restContext, - numFolders - 1, - visibility, - callback, - _folders - ); + return generateTestFoldersWithVisibility(restContext, numFolders - 1, visibility, callback, _folders); } ); }; @@ -153,20 +141,18 @@ const generateTestFoldersWithVisibility = function( */ const setupMultiTenantPrivacyEntities = function(callback) { // Base the folders privacy setup on content. We then create folders to go along with them - ContentTestUtil.setupMultiTenantPrivacyEntities( - (publicTenant, publicTenant1, privateTenant, privateTenant1) => { - // Create the folders - _setupTenant(publicTenant, () => { - _setupTenant(publicTenant1, () => { - _setupTenant(privateTenant, () => { - _setupTenant(privateTenant1, () => { - return callback(publicTenant, publicTenant1, privateTenant, privateTenant1); - }); + ContentTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1, privateTenant, privateTenant1) => { + // Create the folders + _setupTenant(publicTenant, () => { + _setupTenant(publicTenant1, () => { + _setupTenant(privateTenant, () => { + _setupTenant(privateTenant1, () => { + return callback(publicTenant, publicTenant1, privateTenant, privateTenant1); }); }); }); - } - ); + }); + }); }; /** @@ -178,12 +164,7 @@ const setupMultiTenantPrivacyEntities = function(callback) { * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if the content items are not successfully added */ -const assertAddContentItemsToFolderSucceeds = function( - restContext, - folderId, - contentIds, - callback -) { +const assertAddContentItemsToFolderSucceeds = function(restContext, folderId, contentIds, callback) { // First ensure the folder's content library is not stale getAllFolderContentItems(restContext, folderId, null, () => { // Add the content items to the folder @@ -222,13 +203,7 @@ const assertAddContentItemsToFolderSucceeds = function( * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if the request did not fail in the expected manner */ -const assertAddContentItemsToFolderFails = function( - restContext, - folderId, - contentIds, - httpCode, - callback -) { +const assertAddContentItemsToFolderFails = function(restContext, folderId, contentIds, httpCode, callback) { RestAPI.Folders.addContentItemsToFolder(restContext, folderId, contentIds, err => { assert.ok(err); assert.strictEqual(err.code, httpCode); @@ -245,12 +220,7 @@ const assertAddContentItemsToFolderFails = function( * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if the content item is not successfully added to all the folders */ -const assertAddContentItemToFoldersSucceeds = function( - restContext, - folderIds, - contentId, - callback -) { +const assertAddContentItemToFoldersSucceeds = function(restContext, folderIds, contentId, callback) { if (_.isEmpty(folderIds)) { return callback(); } @@ -273,12 +243,7 @@ const assertAddContentItemToFoldersSucceeds = function( * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if the content items are not successfully removed */ -const assertRemoveContentItemsFromFolderSucceeds = function( - restContext, - folderId, - contentIds, - callback -) { +const assertRemoveContentItemsFromFolderSucceeds = function(restContext, folderId, contentIds, callback) { // First ensure the folder's content library is not stale getAllFolderContentItems(restContext, folderId, null, (contentItems, responses) => { // Ensure the items are there in the first place @@ -324,13 +289,7 @@ const assertRemoveContentItemsFromFolderSucceeds = function( * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if the request did not fail in the expected manner */ -const assertRemoveContentItemsFromFolderFails = function( - restContext, - folderId, - contentIds, - httpCode, - callback -) { +const assertRemoveContentItemsFromFolderFails = function(restContext, folderId, contentIds, httpCode, callback) { RestAPI.Folders.removeContentItemsFromFolder(restContext, folderId, contentIds, err => { assert.ok(err); assert.strictEqual(err.code, httpCode); @@ -449,10 +408,7 @@ const assertCreateFolderSucceeds = function( assert.ok(_.isNumber(createdFolder.created)); assert.strictEqual(createdFolder.created, createdFolder.lastModified); assert.strictEqual(createdFolder.profilePath.indexOf('/folder/'), 0); - assert.notStrictEqual( - createdFolder.profilePath.indexOf(createdFolder.id.split(':').pop()), - -1 - ); + assert.notStrictEqual(createdFolder.profilePath.indexOf(createdFolder.id.split(':').pop()), -1); assert.strictEqual(createdFolder.resourceType, 'folder'); // Determine what the full membership should be, including the current user who created @@ -487,21 +443,32 @@ const assertCreateFolderSucceeds = function( AuthzTestUtil.getMemberRolesFromResults(membersAfterCreate) ); - AuthzTestUtil.assertGetInvitationsSucceeds( - restContext, - 'folder', - createdFolder.id, - result => { - AuthzTestUtil.assertEmailRolesEquals( - {}, - expectedMemberRoles, - AuthzTestUtil.getEmailRolesFromResults(result.results) - ); - - // Get the folders libraries after it was created and ensure that the folder is in the libraries - _getAllFoldersInLibraries( - allMemberInfos, - principalFoldersLibrariesAfterCreate => { + AuthzTestUtil.assertGetInvitationsSucceeds(restContext, 'folder', createdFolder.id, result => { + AuthzTestUtil.assertEmailRolesEquals( + {}, + expectedMemberRoles, + AuthzTestUtil.getEmailRolesFromResults(result.results) + ); + + // Get the folders libraries after it was created and ensure that the folder is in the libraries + _getAllFoldersInLibraries(allMemberInfos, principalFoldersLibrariesAfterCreate => { + _.each(allMemberInfos, memberInfo => { + assert.ok( + _.chain(principalFoldersLibrariesAfterCreate[memberInfo.profile.id]) + .pluck('id') + .contains(createdFolder.id) + .value() + ); + }); + + const allMemberInfoIds = _.chain(allMemberInfos) + .pluck('profile') + .pluck('id') + .value(); + + // Purge the member folder libraries and check again to ensure they update properly both on-the-fly and when built from scratch + _purgeFoldersLibraries(allMemberInfoIds, () => { + _getAllFoldersInLibraries(allMemberInfos, principalFoldersLibrariesAfterCreate => { _.each(allMemberInfos, memberInfo => { assert.ok( _.chain(principalFoldersLibrariesAfterCreate[memberInfo.profile.id]) @@ -511,33 +478,11 @@ const assertCreateFolderSucceeds = function( ); }); - const allMemberInfoIds = _.chain(allMemberInfos) - .pluck('profile') - .pluck('id') - .value(); - - // Purge the member folder libraries and check again to ensure they update properly both on-the-fly and when built from scratch - _purgeFoldersLibraries(allMemberInfoIds, () => { - _getAllFoldersInLibraries( - allMemberInfos, - principalFoldersLibrariesAfterCreate => { - _.each(allMemberInfos, memberInfo => { - assert.ok( - _.chain(principalFoldersLibrariesAfterCreate[memberInfo.profile.id]) - .pluck('id') - .contains(createdFolder.id) - .value() - ); - }); - - return callback(createdFolder); - } - ); - }); - } - ); - } - ); + return callback(createdFolder); + }); + }); + }); + }); }); }); }); @@ -633,24 +578,13 @@ const assertUpdateFolderFails = function(restContext, folderId, updates, httpCod * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if the request did not fail in the expected manner */ -const assertUpdateFolderContentVisibilityFails = function( - restContext, - folderId, - visibility, - httpCode, - callback -) { - RestAPI.Folders.updateFolderContentVisibility( - restContext, - folderId, - visibility, - (err, folder) => { - assert.ok(err); - assert.strictEqual(err.code, httpCode); - assert.ok(!folder); - return callback(); - } - ); +const assertUpdateFolderContentVisibilityFails = function(restContext, folderId, visibility, httpCode, callback) { + RestAPI.Folders.updateFolderContentVisibility(restContext, folderId, visibility, (err, folder) => { + assert.ok(err); + assert.strictEqual(err.code, httpCode); + assert.ok(!folder); + return callback(); + }); }; /** @@ -703,14 +637,7 @@ const assertDeleteFolderSucceeds = function(restContext, folderId, deleteContent * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if the request did not fail in the expected manner */ -const assertGetFolderContentLibraryFails = function( - restContext, - folderId, - start, - limit, - httpCode, - callback -) { +const assertGetFolderContentLibraryFails = function(restContext, folderId, start, limit, httpCode, callback) { RestAPI.Folders.getFolderContentLibrary(restContext, folderId, start, limit, (err, result) => { assert.ok(err); assert.strictEqual(err.code, httpCode); @@ -730,13 +657,7 @@ const assertGetFolderContentLibraryFails = function( * @param {Object} callback.result The result object, as per `RestAPI.Folders.getFolderContentLibrary` * @throws {AssertionError} Thrown if the request did not succeed */ -const assertGetFolderContentLibrarySucceeds = function( - restContext, - folderId, - start, - limit, - callback -) { +const assertGetFolderContentLibrarySucceeds = function(restContext, folderId, start, limit, callback) { RestAPI.Folders.getFolderContentLibrary(restContext, folderId, start, limit, (err, result) => { assert.ok(!err); assert.ok(_.isArray(result.results)); @@ -789,14 +710,7 @@ const assertFolderEquals = function(restContext, folderId, contentIds, callback) * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if the request did not fail in the expected manner */ -const assertGetFolderMembersFails = function( - restContext, - folderId, - start, - limit, - httpCode, - callback -) { +const assertGetFolderMembersFails = function(restContext, folderId, start, limit, httpCode, callback) { RestAPI.Folders.getFolderMembers(restContext, folderId, start, limit, (err, result) => { assert.ok(err); assert.strictEqual(err.code, httpCode); @@ -949,14 +863,7 @@ const assertGetFoldersLibrarySucceeds = function(restContext, principalId, start * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if the request did not fail in the expected manner */ -const assertGetFoldersLibraryFails = function( - restContext, - principalId, - start, - limit, - httpCode, - callback -) { +const assertGetFoldersLibraryFails = function(restContext, principalId, start, limit, httpCode, callback) { RestAPI.Folders.getFoldersLibrary(restContext, principalId, start, limit, (err, result) => { assert.ok(err); assert.strictEqual(err.code, httpCode); @@ -976,13 +883,7 @@ const assertGetFoldersLibraryFails = function( * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if the request did not fail in the expected manner */ -const assertRemoveFolderFromLibraryFails = function( - restContext, - principalId, - folderId, - httpCode, - callback -) { +const assertRemoveFolderFromLibraryFails = function(restContext, principalId, folderId, httpCode, callback) { RestAPI.Folders.removeFolderFromLibrary(restContext, principalId, folderId, (err, result) => { assert.ok(err); assert.strictEqual(err.code, httpCode); @@ -1000,12 +901,7 @@ const assertRemoveFolderFromLibraryFails = function( * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if the request failed */ -const assertRemoveFolderFromLibrarySucceeds = function( - restContext, - principalId, - folderId, - callback -) { +const assertRemoveFolderFromLibrarySucceeds = function(restContext, principalId, folderId, callback) { RestAPI.Folders.removeFolderFromLibrary(restContext, principalId, folderId, (err, result) => { assert.ok(!err); @@ -1039,13 +935,7 @@ const assertRemoveFolderFromLibrarySucceeds = function( * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if the request did not succeed */ -const assertShareFolderSucceeds = function( - managerRestContext, - actorRestContext, - folderId, - viewers, - callback -) { +const assertShareFolderSucceeds = function(managerRestContext, actorRestContext, folderId, viewers, callback) { const viewersSplit = _.partition(viewers, viewer => { return _.isString(viewer); }); @@ -1091,60 +981,49 @@ const assertShareFolderSucceeds = function( assert.ok(!err); LibraryAPI.Index.whenUpdatesComplete(() => { // Ensure the invitations and members of the folder were udpated as we would expect - AuthzTestUtil.assertGetInvitationsSucceeds( - managerRestContext, - 'folder', - folderId, - result => { - AuthzTestUtil.assertEmailRolesEquals( - emailRolesBefore, + AuthzTestUtil.assertGetInvitationsSucceeds(managerRestContext, 'folder', folderId, result => { + AuthzTestUtil.assertEmailRolesEquals( + emailRolesBefore, + roleChanges, + AuthzTestUtil.getEmailRolesFromResults(result.results) + ); + + getAllFolderMembers(managerRestContext, folderId, null, results => { + AuthzTestUtil.assertMemberRolesEquals( + memberRolesBefore, roleChanges, - AuthzTestUtil.getEmailRolesFromResults(result.results) + AuthzTestUtil.getMemberRolesFromResults(results) ); - getAllFolderMembers(managerRestContext, folderId, null, results => { - AuthzTestUtil.assertMemberRolesEquals( - memberRolesBefore, - roleChanges, - AuthzTestUtil.getMemberRolesFromResults(results) - ); - - // Ensure the folder libraries of all the principals we shared with contain the folder - _getAllFoldersInLibraries(viewerInfos, principalFoldersLibrariesAfterShare => { - _.each(principalFoldersLibrariesAfterShare, (foldersLibrary, principalId) => { - assert.ok( - _.chain(foldersLibrary) - .pluck('id') - .contains(foldersLibrary, folderId) - ); - }); + // Ensure the folder libraries of all the principals we shared with contain the folder + _getAllFoldersInLibraries(viewerInfos, principalFoldersLibrariesAfterShare => { + _.each(principalFoldersLibrariesAfterShare, (foldersLibrary, principalId) => { + assert.ok( + _.chain(foldersLibrary) + .pluck('id') + .contains(foldersLibrary, folderId) + ); + }); - // Purge the folder libraries of all users to ensure a rebuild will - // still contain the shared folder - _purgeFoldersLibraries(viewerInfoIds, () => { - // Ensure the rebuilt libraries of all the principals we shared with contain the folder - _getAllFoldersInLibraries( - viewerInfos, - principalFoldersLibrariesAfterShare => { - _.each( - principalFoldersLibrariesAfterShare, - (foldersLibrary, principalId) => { - assert.ok( - _.chain(foldersLibrary) - .pluck('id') - .contains(foldersLibrary, folderId) - ); - } - ); - - return callback(); - } - ); + // Purge the folder libraries of all users to ensure a rebuild will + // still contain the shared folder + _purgeFoldersLibraries(viewerInfoIds, () => { + // Ensure the rebuilt libraries of all the principals we shared with contain the folder + _getAllFoldersInLibraries(viewerInfos, principalFoldersLibrariesAfterShare => { + _.each(principalFoldersLibrariesAfterShare, (foldersLibrary, principalId) => { + assert.ok( + _.chain(foldersLibrary) + .pluck('id') + .contains(foldersLibrary, folderId) + ); + }); + + return callback(); }); }); }); - } - ); + }); + }); }); }); }); @@ -1163,14 +1042,7 @@ const assertShareFolderSucceeds = function( * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if the request did not fail in the expected manner */ -const assertShareFolderFails = function( - managerRestContext, - actorRestContext, - folderId, - viewerIds, - httpCode, - callback -) { +const assertShareFolderFails = function(managerRestContext, actorRestContext, folderId, viewerIds, httpCode, callback) { RestAPI.Folders.shareFolder(actorRestContext, folderId, viewerIds, err => { assert.ok(err); assert.strictEqual(err.code, httpCode); @@ -1265,46 +1137,41 @@ const assertUpdateFolderMembersSucceeds = function( assert.ok(!err); // Ensure the member and email roles are as we expect given the change made - AuthzTestUtil.assertGetInvitationsSucceeds( - managerRestContext, - 'folder', - folderId, - result => { - AuthzTestUtil.assertEmailRolesEquals( - emailRolesBefore, + AuthzTestUtil.assertGetInvitationsSucceeds(managerRestContext, 'folder', folderId, result => { + AuthzTestUtil.assertEmailRolesEquals( + emailRolesBefore, + roleChange, + AuthzTestUtil.getEmailRolesFromResults(result.results) + ); + getAllFolderMembers(managerRestContext, folderId, null, results => { + AuthzTestUtil.assertMemberRolesEquals( + memberRolesBefore, roleChange, - AuthzTestUtil.getEmailRolesFromResults(result.results) + AuthzTestUtil.getMemberRolesFromResults(results) ); - getAllFolderMembers(managerRestContext, folderId, null, results => { - AuthzTestUtil.assertMemberRolesEquals( - memberRolesBefore, - roleChange, - AuthzTestUtil.getMemberRolesFromResults(results) - ); - // For all the members we had a rest context for, ensure their libraries are updated - // appropriately to contain (or not contain) this folder - _getAllFoldersInLibraries(memberInfos, principalFoldersLibrariesAfterUpdate => { - _.each(roleChange, (change, memberId) => { - const foldersLibrary = principalFoldersLibrariesAfterUpdate[memberId]; - if (foldersLibrary) { - const containsFolder = _.chain(foldersLibrary) - .pluck('id') - .contains(folderId) - .value(); - if (change === false) { - assert.ok(!containsFolder); - } else { - assert.ok(containsFolder); - } + // For all the members we had a rest context for, ensure their libraries are updated + // appropriately to contain (or not contain) this folder + _getAllFoldersInLibraries(memberInfos, principalFoldersLibrariesAfterUpdate => { + _.each(roleChange, (change, memberId) => { + const foldersLibrary = principalFoldersLibrariesAfterUpdate[memberId]; + if (foldersLibrary) { + const containsFolder = _.chain(foldersLibrary) + .pluck('id') + .contains(folderId) + .value(); + if (change === false) { + assert.ok(!containsFolder); + } else { + assert.ok(containsFolder); } - }); - - return callback(); + } }); + + return callback(); }); - } - ); + }); + }); }); }); }); @@ -1322,13 +1189,7 @@ const assertUpdateFolderMembersSucceeds = function( * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if any assertions failed */ -const assertFullFoldersLibraryEquals = function( - restContext, - principalId, - expectedFolderIds, - ensureOrder, - callback -) { +const assertFullFoldersLibraryEquals = function(restContext, principalId, expectedFolderIds, ensureOrder, callback) { getAllFoldersInLibrary(restContext, principalId, null, folders => { const actualFolderIds = _.pluck(folders, 'id'); @@ -1356,25 +1217,19 @@ const assertFullFoldersLibraryEquals = function( * @throws {AssertionError} Thrown if any assertions failed */ const assertFolderSearchEquals = function(restContext, folderId, q, expectedContent, callback) { - SearchTestUtil.searchAll( - restContext, - 'folder-content', - [folderId], - { q, scope: '_network' }, - (err, results) => { - assert.ok(!err); + SearchTestUtil.searchAll(restContext, 'folder-content', [folderId], { q, scope: '_network' }, (err, results) => { + assert.ok(!err); - // Assert we've got the exact number of results that we expected (in case we want 0 results) - assert.strictEqual(results.results.length, expectedContent.length); + // Assert we've got the exact number of results that we expected (in case we want 0 results) + assert.strictEqual(results.results.length, expectedContent.length); - // Assert that the results that came back are the ones we expected - _.each(expectedContent, content => { - assert.ok(_.findWhere(results.results, { id: content.id })); - }); + // Assert that the results that came back are the ones we expected + _.each(expectedContent, content => { + assert.ok(_.findWhere(results.results, { id: content.id })); + }); - return callback(); - } - ); + return callback(); + }); }; /** @@ -1387,13 +1242,7 @@ const assertFolderSearchEquals = function(restContext, folderId, q, expectedCont * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if any assertions failed */ -const assertGeneralFolderSearchEquals = function( - restContext, - q, - expectedFolders, - missingFolders, - callback -) { +const assertGeneralFolderSearchEquals = function(restContext, q, expectedFolders, missingFolders, callback) { SearchTestUtil.searchAll( restContext, 'general', @@ -1418,14 +1267,7 @@ const assertGeneralFolderSearchEquals = function( * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if any assertions failed */ -const assertFolderLibrarySearch = function( - restContext, - principalId, - q, - expectedFolders, - missingFolders, - callback -) { +const assertFolderLibrarySearch = function(restContext, principalId, q, expectedFolders, missingFolders, callback) { SearchTestUtil.searchAll(restContext, 'folder-library', [principalId], { q }, (err, results) => { assert.ok(!err); _assertSearchResults(results, expectedFolders, missingFolders); @@ -1454,10 +1296,7 @@ const _assertSearchResults = function(results, expectedFolders, missingFolders) assert.strictEqual(searchResult.visibility, folder.visibility); const tenantAlias = folder.tenant.alias; const { resourceId } = AuthzUtil.getResourceFromId(folder.id); - assert.strictEqual( - searchResult.profilePath, - util.format('/folder/%s/%s', tenantAlias, resourceId) - ); + assert.strictEqual(searchResult.profilePath, util.format('/folder/%s/%s', tenantAlias, resourceId)); // If the folder has a thumbnail, we assert the search result has it as well if (folder.previews && folder.previews.thumbnailUri) { @@ -1502,12 +1341,7 @@ const assertFolderSearchFails = function(restContext, folderId, httpCode, callba * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if any assertions failed */ -const assertFullFolderMembersEquals = function( - restContext, - folderId, - expectedMemberRoles, - callback -) { +const assertFullFolderMembersEquals = function(restContext, folderId, expectedMemberRoles, callback) { // Remove any roles that contain a role of `false` as they would have been removed expectedMemberRoles = _.extend({}, expectedMemberRoles); _.each(expectedMemberRoles, (role, userId) => { @@ -1541,15 +1375,7 @@ const assertFullFolderMembersEquals = function( * @param {Folder[]} callback.folders A list of all folders in the library * @param {Object[]} callback.responses All the raw web responses that were received for each page request */ -const getAllFoldersInLibrary = function( - restContext, - principalId, - opts, - callback, - _nextToken, - _folders, - _responses -) { +const getAllFoldersInLibrary = function(restContext, principalId, opts, callback, _nextToken, _folders, _responses) { opts = opts || {}; opts.batchSize = opts.batchSize || 25; _folders = _folders || []; @@ -1601,24 +1427,18 @@ const getAllFolderContentItems = function( return callback(_contentItems, _responses); } - assertGetFolderContentLibrarySucceeds( - restContext, - folderId, - _nextToken, - opts.batchSize, - result => { - _responses.push(result); - return getAllFolderContentItems( - restContext, - folderId, - opts, - callback, - result.nextToken, - _.union(_contentItems, result.results), - _responses - ); - } - ); + assertGetFolderContentLibrarySucceeds(restContext, folderId, _nextToken, opts.batchSize, result => { + _responses.push(result); + return getAllFolderContentItems( + restContext, + folderId, + opts, + callback, + result.nextToken, + _.union(_contentItems, result.results), + _responses + ); + }); }; /** @@ -1632,15 +1452,7 @@ const getAllFolderContentItems = function( * @param {Principal[]} callback.members A list of all users and groups who are members of the folder * @param {Object[]} callback.responses All the raw web responses that were received for each page request */ -const getAllFolderMembers = function( - restContext, - folderId, - opts, - callback, - _nextToken, - _members, - _responses -) { +const getAllFolderMembers = function(restContext, folderId, opts, callback, _nextToken, _members, _responses) { opts = opts || {}; opts.batchSize = opts.batchSize || 25; _members = _members || []; @@ -1674,14 +1486,7 @@ const getAllFolderMembers = function( * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if the request did not fail in the expected manner */ -const assertCreateMessageFails = function( - restContext, - folderId, - body, - replyTo, - httpCode, - callback -) { +const assertCreateMessageFails = function(restContext, folderId, body, replyTo, httpCode, callback) { RestAPI.Folders.createMessage(restContext, folderId, body, replyTo, err => { assert.ok(err); assert.strictEqual(err.code, httpCode); @@ -1753,13 +1558,7 @@ const assertGetMessagesSucceeds = function(restContext, folderId, start, limit, * @param {Function} callback Standard callback function * @throws {AssertionError} Thrown if the request did not fail in the expected manner */ -const assertDeleteMessageFails = function( - restContext, - folderId, - messageCreated, - httpCode, - callback -) { +const assertDeleteMessageFails = function(restContext, folderId, messageCreated, httpCode, callback) { RestAPI.Folders.deleteMessage(restContext, folderId, messageCreated, (err, result) => { assert.ok(err); assert.strictEqual(err.code, httpCode); @@ -1792,15 +1591,12 @@ const assertDeleteMessageSucceeds = function(restContext, folderId, messageCreat * @api private */ const _setupTenant = function(tenant, callback) { - _createMultiPrivacyFolders( - tenant.adminRestContext, - (publicFolder, loggedinFolder, privateFolder) => { - tenant.publicFolder = publicFolder; - tenant.loggedinFolder = loggedinFolder; - tenant.privateFolder = privateFolder; - return callback(); - } - ); + _createMultiPrivacyFolders(tenant.adminRestContext, (publicFolder, loggedinFolder, privateFolder) => { + tenant.publicFolder = publicFolder; + tenant.loggedinFolder = loggedinFolder; + tenant.privateFolder = privateFolder; + return callback(); + }); }; /** @@ -1948,7 +1744,7 @@ const _generalizePrincipalInfoModel = function(principalInfo) { return principalInfo; }; -module.exports = { +export { generateTestFolders, generateTestFoldersWithVisibility, setupMultiTenantPrivacyEntities, diff --git a/packages/oae-folders/tests/test-activity.js b/packages/oae-folders/tests/test-activity.js index 40b927c2b9..5bbbf844f3 100644 --- a/packages/oae-folders/tests/test-activity.js +++ b/packages/oae-folders/tests/test-activity.js @@ -13,26 +13,25 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityTestsUtil = require('oae-activity/lib/test/util'); -const AuthzUtil = require('oae-authz/lib/util'); -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as ActivityTestsUtil from 'oae-activity/lib/test/util'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; -const { FoldersConstants } = require('oae-folders/lib/constants'); -const FoldersDAO = require('oae-folders/lib/internal/dao'); -const FoldersLibrary = require('oae-folders/lib/library'); -const FoldersTestUtil = require('oae-folders/lib/test/util'); +import { FoldersConstants } from 'oae-folders/lib/constants'; +import * as FoldersDAO from 'oae-folders/lib/internal/dao'; +import * as FoldersTestUtil from 'oae-folders/lib/test/util'; describe('Folders - Activity', () => { let camAdminRestContext = null; /*! - * Set up an admin REST context before the tests - */ + * Set up an admin REST context before the tests + */ before(callback => { camAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.cam.host); return callback(); @@ -67,31 +66,16 @@ describe('Folders - Activity', () => { assert.ok(!err); const groupBmembers = {}; groupBmembers[groupMemberB.user.id] = 'member'; - RestAPI.Group.setGroupMembers( - simong.restContext, - groupB.group.id, - groupBmembers, - err => { - assert.ok(!err); + RestAPI.Group.setGroupMembers(simong.restContext, groupB.group.id, groupBmembers, err => { + assert.ok(!err); - // Nico follows simong - RestAPI.Following.follow(nico.restContext, simong.user.id, err => { - assert.ok(!err); + // Nico follows simong + RestAPI.Following.follow(nico.restContext, simong.user.id, err => { + assert.ok(!err); - return callback( - simong, - nico, - bert, - stuart, - stephen, - groupMemberA, - groupMemberB, - groupA, - groupB - ); - }); - } - ); + return callback(simong, nico, bert, stuart, stephen, groupMemberA, groupMemberB, groupA, groupB); + }); + }); }); }); } @@ -671,8 +655,7 @@ describe('Folders - Activity', () => { // Only managers should receive a notification ActivityTestsUtil.assertNotificationStreamContainsActivity( bert.restContext, - FoldersConstants.activity - .ACTIVITY_FOLDER_UPDATE_VISIBILITY, + FoldersConstants.activity.ACTIVITY_FOLDER_UPDATE_VISIBILITY, ActivityConstants.verbs.UPDATE, simong.user.id, folder.id, @@ -681,8 +664,7 @@ describe('Folders - Activity', () => { // Non members get nothing ActivityTestsUtil.assertNotificationStreamDoesNotContainActivity( nico.restContext, - FoldersConstants.activity - .ACTIVITY_FOLDER_UPDATE_VISIBILITY, + FoldersConstants.activity.ACTIVITY_FOLDER_UPDATE_VISIBILITY, callback ); } @@ -794,20 +776,17 @@ describe('Folders - Activity', () => { ActivityTestsUtil.assertFeedDoesNotContainActivity( simong.restContext, simong.user.id, - FoldersConstants.activity - .ACTIVITY_FOLDER_UPDATE_MEMBER_ROLE, + FoldersConstants.activity.ACTIVITY_FOLDER_UPDATE_MEMBER_ROLE, () => { ActivityTestsUtil.assertFeedDoesNotContainActivity( bert.restContext, bert.user.id, - FoldersConstants.activity - .ACTIVITY_FOLDER_UPDATE_MEMBER_ROLE, + FoldersConstants.activity.ACTIVITY_FOLDER_UPDATE_MEMBER_ROLE, () => { ActivityTestsUtil.assertFeedDoesNotContainActivity( groupMemberA.restContext, groupA.group.id, - FoldersConstants.activity - .ACTIVITY_FOLDER_UPDATE_MEMBER_ROLE, + FoldersConstants.activity.ACTIVITY_FOLDER_UPDATE_MEMBER_ROLE, callback ); } @@ -912,18 +891,15 @@ describe('Folders - Activity', () => { () => { ActivityTestsUtil.assertNotificationStreamDoesNotContainActivity( bert.restContext, - FoldersConstants.activity - .ACTIVITY_FOLDER_UPDATE_MEMBER_ROLE, + FoldersConstants.activity.ACTIVITY_FOLDER_UPDATE_MEMBER_ROLE, () => { ActivityTestsUtil.assertNotificationStreamDoesNotContainActivity( stuart.restContext, - FoldersConstants.activity - .ACTIVITY_FOLDER_UPDATE_MEMBER_ROLE, + FoldersConstants.activity.ACTIVITY_FOLDER_UPDATE_MEMBER_ROLE, () => { ActivityTestsUtil.assertNotificationStreamDoesNotContainActivity( stephen.restContext, - FoldersConstants.activity - .ACTIVITY_FOLDER_UPDATE_MEMBER_ROLE, + FoldersConstants.activity.ACTIVITY_FOLDER_UPDATE_MEMBER_ROLE, callback ); } @@ -964,103 +940,97 @@ describe('Folders - Activity', () => { [], folder => { // Stuart comments on the folder - FoldersTestUtil.assertCreateMessageSucceeds( - stuart.restContext, - folder.id, - 'Message body', - null, - message => { - assert.ok(message); + FoldersTestUtil.assertCreateMessageSucceeds(stuart.restContext, folder.id, 'Message body', null, message => { + assert.ok(message); - // Stuart should have a folder-comment activity - ActivityTestsUtil.assertFeedContainsActivity( - stuart.restContext, - stuart.user.id, - FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, - ActivityConstants.verbs.POST, - stuart.user.id, - message.id, - folder.id, - () => { - // Simon should see the activity - ActivityTestsUtil.assertFeedContainsActivity( - simong.restContext, - simong.user.id, - FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, - ActivityConstants.verbs.POST, - stuart.user.id, - message.id, - folder.id, - () => { - // Unrelated users don't see it - ActivityTestsUtil.assertFeedDoesNotContainActivity( - bert.restContext, - bert.user.id, - FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, - () => { - ActivityTestsUtil.assertFeedDoesNotContainActivity( - stephen.restContext, - stephen.user.id, - FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, - () => { - // When Simon makes a comment, stuart should see it as he's considered to be a recent contributor - FoldersTestUtil.assertCreateMessageSucceeds( - simong.restContext, - folder.id, - 'Message body', - null, - message2 => { - ActivityTestsUtil.assertFeedContainsActivity( - stuart.restContext, - stuart.user.id, - FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, - ActivityConstants.verbs.POST, - [stuart.user.id, simong.user.id], - [message.id, message2.id], - folder.id, - () => { - // Simon should see the activity - ActivityTestsUtil.assertFeedContainsActivity( - simong.restContext, - simong.user.id, - FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, - ActivityConstants.verbs.POST, - [stuart.user.id, simong.user.id], - [message.id, message2.id], - folder.id, - () => { - // Unrelated users don't see it - ActivityTestsUtil.assertFeedDoesNotContainActivity( - bert.restContext, - bert.user.id, - FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, - () => { - ActivityTestsUtil.assertFeedDoesNotContainActivity( - stephen.restContext, - stephen.user.id, - FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + // Stuart should have a folder-comment activity + ActivityTestsUtil.assertFeedContainsActivity( + stuart.restContext, + stuart.user.id, + FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, + ActivityConstants.verbs.POST, + stuart.user.id, + message.id, + folder.id, + () => { + // Simon should see the activity + ActivityTestsUtil.assertFeedContainsActivity( + simong.restContext, + simong.user.id, + FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, + ActivityConstants.verbs.POST, + stuart.user.id, + message.id, + folder.id, + () => { + // Unrelated users don't see it + ActivityTestsUtil.assertFeedDoesNotContainActivity( + bert.restContext, + bert.user.id, + FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, + () => { + ActivityTestsUtil.assertFeedDoesNotContainActivity( + stephen.restContext, + stephen.user.id, + FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, + () => { + // When Simon makes a comment, stuart should see it as he's considered to be a recent contributor + FoldersTestUtil.assertCreateMessageSucceeds( + simong.restContext, + folder.id, + 'Message body', + null, + message2 => { + ActivityTestsUtil.assertFeedContainsActivity( + stuart.restContext, + stuart.user.id, + FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, + ActivityConstants.verbs.POST, + [stuart.user.id, simong.user.id], + [message.id, message2.id], + folder.id, + () => { + // Simon should see the activity + ActivityTestsUtil.assertFeedContainsActivity( + simong.restContext, + simong.user.id, + FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, + ActivityConstants.verbs.POST, + [stuart.user.id, simong.user.id], + [message.id, message2.id], + folder.id, + () => { + // Unrelated users don't see it + ActivityTestsUtil.assertFeedDoesNotContainActivity( + bert.restContext, + bert.user.id, + FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, + () => { + ActivityTestsUtil.assertFeedDoesNotContainActivity( + stephen.restContext, + stephen.user.id, + FoldersConstants.activity.ACTIVITY_FOLDER_COMMENT, + () => { + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); } ); }); @@ -1079,155 +1049,137 @@ describe('Folders - Activity', () => { [], [], folder => { - FoldersTestUtil.assertCreateMessageSucceeds( - simong.restContext, - folder.id, - 'Message body', - null, - message => { - assert.ok(message); + FoldersTestUtil.assertCreateMessageSucceeds(simong.restContext, folder.id, 'Message body', null, message => { + assert.ok(message); - ActivityTestsUtil.collectAndGetActivityStream( - simong.restContext, - simong.user.id, - null, - (err, activityStream) => { - assert.ok(!err); - const entity = activityStream.items[0]; + ActivityTestsUtil.collectAndGetActivityStream( + simong.restContext, + simong.user.id, + null, + (err, activityStream) => { + assert.ok(!err); + const entity = activityStream.items[0]; - // Assert the correct entities are all present - ActivityTestsUtil.assertActivity( - entity, - 'folder-comment', - 'post', - simong.user.id, - message.id, - folder.id - ); + // Assert the correct entities are all present + ActivityTestsUtil.assertActivity( + entity, + 'folder-comment', + 'post', + simong.user.id, + message.id, + folder.id + ); - // Assert the folder information is available on the target - assert.strictEqual(entity.target.displayName, folder.displayName); - assert.strictEqual(entity.target['oae:profilePath'], folder.profilePath); + // Assert the folder information is available on the target + assert.strictEqual(entity.target.displayName, folder.displayName); + assert.strictEqual(entity.target['oae:profilePath'], folder.profilePath); - // Assert the comment information is available on the object - assert.strictEqual(entity.object['oae:messageBoxId'], message.messageBoxId); - assert.strictEqual(entity.object['oae:threadKey'], message.threadKey); - assert.strictEqual(entity.object.content, message.body); - assert.strictEqual(entity.object.published, message.created); - assert.strictEqual(entity.object.objectType, 'folder-comment'); - assert.strictEqual( - entity.object.id, - 'http://' + - global.oaeTests.tenants.cam.host + - '/api/folder/' + - folder.id + - '/messages/' + - message.created - ); + // Assert the comment information is available on the object + assert.strictEqual(entity.object['oae:messageBoxId'], message.messageBoxId); + assert.strictEqual(entity.object['oae:threadKey'], message.threadKey); + assert.strictEqual(entity.object.content, message.body); + assert.strictEqual(entity.object.published, message.created); + assert.strictEqual(entity.object.objectType, 'folder-comment'); + assert.strictEqual( + entity.object.id, + 'http://' + + global.oaeTests.tenants.cam.host + + '/api/folder/' + + folder.id + + '/messages/' + + message.created + ); - // Nico replies - FoldersTestUtil.assertCreateMessageSucceeds( - nico.restContext, - folder.id, - 'A reply', - message.created, - nicosMessage => { - assert.ok(nicosMessage); + // Nico replies + FoldersTestUtil.assertCreateMessageSucceeds( + nico.restContext, + folder.id, + 'A reply', + message.created, + nicosMessage => { + assert.ok(nicosMessage); - ActivityTestsUtil.collectAndGetActivityStream( - simong.restContext, - simong.user.id, - null, - (err, activityStream) => { - assert.ok(!err); - const entity = activityStream.items[0]; + ActivityTestsUtil.collectAndGetActivityStream( + simong.restContext, + simong.user.id, + null, + (err, activityStream) => { + assert.ok(!err); + const entity = activityStream.items[0]; - // Assert the correct entities are all present. The first item should be - // an aggregated `folder-comment` activity. The object and actor will now - // be collections rather than a single message/person - ActivityTestsUtil.assertActivity( - entity, - 'folder-comment', - 'post', - [simong.user.id, nico.user.id], - [message.id, nicosMessage.id], - folder.id - ); + // Assert the correct entities are all present. The first item should be + // an aggregated `folder-comment` activity. The object and actor will now + // be collections rather than a single message/person + ActivityTestsUtil.assertActivity( + entity, + 'folder-comment', + 'post', + [simong.user.id, nico.user.id], + [message.id, nicosMessage.id], + folder.id + ); - // The object should be an oae:collection containing 2 messages (the original message and the reply) - assert.strictEqual(entity.object.objectType, 'collection'); - assert.ok(entity.object['oae:collection']); - assert.strictEqual(entity.object['oae:collection'].length, 2); - const originalMessage = _.find( - entity.object['oae:collection'], - activityMessage => { - return activityMessage['oae:id'] === message.id; - } - ); - assert.ok(originalMessage); - assert.strictEqual(originalMessage['oae:id'], message.id); - assert.strictEqual(originalMessage.content, message.body); - assert.strictEqual(originalMessage.author['oae:id'], simong.user.id); - assert.strictEqual( - originalMessage['oae:tenant'].alias, - global.oaeTests.tenants.cam.alias - ); + // The object should be an oae:collection containing 2 messages (the original message and the reply) + assert.strictEqual(entity.object.objectType, 'collection'); + assert.ok(entity.object['oae:collection']); + assert.strictEqual(entity.object['oae:collection'].length, 2); + const originalMessage = _.find(entity.object['oae:collection'], activityMessage => { + return activityMessage['oae:id'] === message.id; + }); + assert.ok(originalMessage); + assert.strictEqual(originalMessage['oae:id'], message.id); + assert.strictEqual(originalMessage.content, message.body); + assert.strictEqual(originalMessage.author['oae:id'], simong.user.id); + assert.strictEqual(originalMessage['oae:tenant'].alias, global.oaeTests.tenants.cam.alias); - // Assert the reply contains all the correct information - const reply = _.find(entity.object['oae:collection'], activityMessage => { - return activityMessage['oae:id'] === nicosMessage.id; - }); - assert.ok(reply); - assert.strictEqual(reply['oae:id'], nicosMessage.id); - assert.strictEqual(reply['oae:messageBoxId'], nicosMessage.messageBoxId); - assert.strictEqual(reply['oae:threadKey'], nicosMessage.threadKey); - assert.strictEqual( - reply['oae:tenant'].alias, - global.oaeTests.tenants.cam.alias - ); - assert.strictEqual(reply.content, nicosMessage.body); - assert.strictEqual(reply.published, nicosMessage.created); - assert.strictEqual(reply.author['oae:id'], nico.user.id); - assert.ok(reply.inReplyTo); - assert.strictEqual(reply.inReplyTo['oae:id'], message.id); + // Assert the reply contains all the correct information + const reply = _.find(entity.object['oae:collection'], activityMessage => { + return activityMessage['oae:id'] === nicosMessage.id; + }); + assert.ok(reply); + assert.strictEqual(reply['oae:id'], nicosMessage.id); + assert.strictEqual(reply['oae:messageBoxId'], nicosMessage.messageBoxId); + assert.strictEqual(reply['oae:threadKey'], nicosMessage.threadKey); + assert.strictEqual(reply['oae:tenant'].alias, global.oaeTests.tenants.cam.alias); + assert.strictEqual(reply.content, nicosMessage.body); + assert.strictEqual(reply.published, nicosMessage.created); + assert.strictEqual(reply.author['oae:id'], nico.user.id); + assert.ok(reply.inReplyTo); + assert.strictEqual(reply.inReplyTo['oae:id'], message.id); - // Verify both actors are present - assert.strictEqual(entity.actor.objectType, 'collection'); - const simonEntity = _.find(entity.actor['oae:collection'], userEntity => { - return userEntity['oae:id'] === simong.user.id; - }); - assert.ok(simonEntity); - assert.strictEqual(simonEntity['oae:id'], simong.user.id); - assert.strictEqual( - simonEntity['oae:profilePath'], - '/user/' + - simong.user.tenant.alias + - '/' + - AuthzUtil.getResourceFromId(simong.user.id).resourceId - ); + // Verify both actors are present + assert.strictEqual(entity.actor.objectType, 'collection'); + const simonEntity = _.find(entity.actor['oae:collection'], userEntity => { + return userEntity['oae:id'] === simong.user.id; + }); + assert.ok(simonEntity); + assert.strictEqual(simonEntity['oae:id'], simong.user.id); + assert.strictEqual( + simonEntity['oae:profilePath'], + '/user/' + + simong.user.tenant.alias + + '/' + + AuthzUtil.getResourceFromId(simong.user.id).resourceId + ); - const nicoEntity = _.find(entity.actor['oae:collection'], userEntity => { - return userEntity['oae:id'] === nico.user.id; - }); - assert.ok(nicoEntity); - assert.strictEqual(nicoEntity['oae:id'], nico.user.id); - assert.strictEqual( - nicoEntity['oae:profilePath'], - '/user/' + - nico.user.tenant.alias + - '/' + - AuthzUtil.getResourceFromId(nico.user.id).resourceId - ); + const nicoEntity = _.find(entity.actor['oae:collection'], userEntity => { + return userEntity['oae:id'] === nico.user.id; + }); + assert.ok(nicoEntity); + assert.strictEqual(nicoEntity['oae:id'], nico.user.id); + assert.strictEqual( + nicoEntity['oae:profilePath'], + '/user/' + nico.user.tenant.alias + '/' + AuthzUtil.getResourceFromId(nico.user.id).resourceId + ); - return callback(); - } - ); - } - ); - } - ); - } - ); + return callback(); + } + ); + } + ); + } + ); + }); } ); }); @@ -1335,18 +1287,15 @@ describe('Folders - Activity', () => { // Others shouldn't receive a notification ActivityTestsUtil.assertNotificationStreamDoesNotContainActivity( nico.restContext, - FoldersConstants.activity - .ACTIVITY_FOLDER_ADD_TO_FOLDER, + FoldersConstants.activity.ACTIVITY_FOLDER_ADD_TO_FOLDER, () => { ActivityTestsUtil.assertNotificationStreamDoesNotContainActivity( stephen.restContext, - FoldersConstants.activity - .ACTIVITY_FOLDER_ADD_TO_FOLDER, + FoldersConstants.activity.ACTIVITY_FOLDER_ADD_TO_FOLDER, () => { ActivityTestsUtil.assertNotificationStreamDoesNotContainActivity( stuart.restContext, - FoldersConstants.activity - .ACTIVITY_FOLDER_ADD_TO_FOLDER, + FoldersConstants.activity.ACTIVITY_FOLDER_ADD_TO_FOLDER, callback ); } @@ -1453,14 +1402,8 @@ describe('Folders - Activity', () => { assert.strictEqual(addToFolderActivities.length, 2); // Sanity-check we have an activity for each folder - assert.strictEqual( - addToFolderActivities[0].target['oae:id'], - folderB.id - ); - assert.strictEqual( - addToFolderActivities[1].target['oae:id'], - folderA.id - ); + assert.strictEqual(addToFolderActivities[0].target['oae:id'], folderB.id); + assert.strictEqual(addToFolderActivities[1].target['oae:id'], folderA.id); return callback(); } ); @@ -1504,29 +1447,24 @@ describe('Folders - Activity', () => { assert.ok(!err); // Get the activities - ActivityTestsUtil.collectAndGetActivityStream( - simong.restContext, - simong.user.id, - null, - (err, response) => { - assert.ok(!err); + ActivityTestsUtil.collectAndGetActivityStream(simong.restContext, simong.user.id, null, (err, response) => { + assert.ok(!err); - // Assert the activity is present - const createdFolderActivity = _.findWhere(response.items, { - 'oae:activityType': FoldersConstants.activity.ACTIVITY_FOLDER_CREATE - }); - assert.ok(createdFolderActivity); + // Assert the activity is present + const createdFolderActivity = _.findWhere(response.items, { + 'oae:activityType': FoldersConstants.activity.ACTIVITY_FOLDER_CREATE + }); + assert.ok(createdFolderActivity); - // Assert the folder has a thumbnail and wide image - assert.ok(createdFolderActivity.object); - assert.ok(createdFolderActivity.object.image); - assert.ok(createdFolderActivity.object.image.url); - assert.ok(createdFolderActivity.object['oae:wideImage']); - assert.ok(createdFolderActivity.object['oae:wideImage'].url); + // Assert the folder has a thumbnail and wide image + assert.ok(createdFolderActivity.object); + assert.ok(createdFolderActivity.object.image); + assert.ok(createdFolderActivity.object.image.url); + assert.ok(createdFolderActivity.object['oae:wideImage']); + assert.ok(createdFolderActivity.object['oae:wideImage'].url); - return callback(); - } - ); + return callback(); + }); }); } ); @@ -1596,134 +1534,130 @@ describe('Folders - Activity', () => { * are routed to the correct activity stream */ it('verify content-create activities are routed to the correct activity streams and contain the correct target information', callback => { - TestsUtil.generateTestUsers( - camAdminRestContext, - 4, - (err, users, simong, nico, bert, stuart) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 4, (err, users, simong, nico, bert, stuart) => { + assert.ok(!err); - TestsUtil.generateTestGroups(nico.restContext, 2, (nicosGroup1, nicosGroup2) => { - TestsUtil.generateTestGroups(bert.restContext, 1, bertsGroup => { - FoldersTestUtil.assertCreateFolderSucceeds( - simong.restContext, - 'test displayName', - 'test description', - 'public', - [nicosGroup1, bertsGroup, stuart], - [], - folder1 => { - FoldersTestUtil.assertCreateFolderSucceeds( - simong.restContext, - 'test displayName', - 'test description', - 'public', - [nicosGroup2], - [], - folder2 => { - RestAPI.Content.createLink( - simong.restContext, - 'test', - 'test', - 'public', - 'http://www.google.ca', - null, - null, - [folder1.id, folder2.id], - (err, link) => { - assert.ok(!err); + TestsUtil.generateTestGroups(nico.restContext, 2, (nicosGroup1, nicosGroup2) => { + TestsUtil.generateTestGroups(bert.restContext, 1, bertsGroup => { + FoldersTestUtil.assertCreateFolderSucceeds( + simong.restContext, + 'test displayName', + 'test description', + 'public', + [nicosGroup1, bertsGroup, stuart], + [], + folder1 => { + FoldersTestUtil.assertCreateFolderSucceeds( + simong.restContext, + 'test displayName', + 'test description', + 'public', + [nicosGroup2], + [], + folder2 => { + RestAPI.Content.createLink( + simong.restContext, + 'test', + 'test', + 'public', + 'http://www.google.ca', + null, + null, + [folder1.id, folder2.id], + (err, link) => { + assert.ok(!err); - // Simon sees both folders - ActivityTestsUtil.assertFeedContainsActivity( - simong.restContext, - simong.user.id, - 'content-create', - ActivityConstants.verbs.CREATE, - simong.user.id, - link.id, - null, - () => { - // Nico sees both folders in his personal stream and each one - // distinctly in his two groups - ActivityTestsUtil.assertFeedContainsActivity( - nico.restContext, - nico.user.id, - 'content-create', - ActivityConstants.verbs.CREATE, - simong.user.id, - link.id, - null, - () => { - ActivityTestsUtil.assertFeedContainsActivity( - nico.restContext, - nicosGroup1.group.id, - 'content-create', - ActivityConstants.verbs.CREATE, - simong.user.id, - link.id, - null, - () => { - ActivityTestsUtil.assertFeedContainsActivity( - nico.restContext, - nicosGroup2.group.id, - 'content-create', - ActivityConstants.verbs.CREATE, - simong.user.id, - link.id, - null, - () => { - // Bert only sees folder1 in his personal stream and in his group's stream - ActivityTestsUtil.assertFeedContainsActivity( - bert.restContext, - bert.user.id, - 'content-create', - ActivityConstants.verbs.CREATE, - simong.user.id, - link.id, - null, - () => { - ActivityTestsUtil.assertFeedContainsActivity( - bert.restContext, - bertsGroup.group.id, - 'content-create', - ActivityConstants.verbs.CREATE, - simong.user.id, - link.id, - null, - () => { - // Stuart only sees the first folder - ActivityTestsUtil.assertFeedContainsActivity( - stuart.restContext, - stuart.user.id, - 'content-create', - ActivityConstants.verbs.CREATE, - simong.user.id, - link.id, - null, - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - }); + // Simon sees both folders + ActivityTestsUtil.assertFeedContainsActivity( + simong.restContext, + simong.user.id, + 'content-create', + ActivityConstants.verbs.CREATE, + simong.user.id, + link.id, + null, + () => { + // Nico sees both folders in his personal stream and each one + // distinctly in his two groups + ActivityTestsUtil.assertFeedContainsActivity( + nico.restContext, + nico.user.id, + 'content-create', + ActivityConstants.verbs.CREATE, + simong.user.id, + link.id, + null, + () => { + ActivityTestsUtil.assertFeedContainsActivity( + nico.restContext, + nicosGroup1.group.id, + 'content-create', + ActivityConstants.verbs.CREATE, + simong.user.id, + link.id, + null, + () => { + ActivityTestsUtil.assertFeedContainsActivity( + nico.restContext, + nicosGroup2.group.id, + 'content-create', + ActivityConstants.verbs.CREATE, + simong.user.id, + link.id, + null, + () => { + // Bert only sees folder1 in his personal stream and in his group's stream + ActivityTestsUtil.assertFeedContainsActivity( + bert.restContext, + bert.user.id, + 'content-create', + ActivityConstants.verbs.CREATE, + simong.user.id, + link.id, + null, + () => { + ActivityTestsUtil.assertFeedContainsActivity( + bert.restContext, + bertsGroup.group.id, + 'content-create', + ActivityConstants.verbs.CREATE, + simong.user.id, + link.id, + null, + () => { + // Stuart only sees the first folder + ActivityTestsUtil.assertFeedContainsActivity( + stuart.restContext, + stuart.user.id, + 'content-create', + ActivityConstants.verbs.CREATE, + simong.user.id, + link.id, + null, + () => { + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); }); - } - ); + }); + }); }); }); diff --git a/packages/oae-folders/tests/test-folders.js b/packages/oae-folders/tests/test-folders.js index acfc895c24..f9454c6039 100644 --- a/packages/oae-folders/tests/test-folders.js +++ b/packages/oae-folders/tests/test-folders.js @@ -13,23 +13,22 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const util = require('util'); -const _ = require('underscore'); -const path = require('path'); - -const AuthzAPI = require('oae-authz'); -const AuthzTestUtil = require('oae-authz/lib/test/util'); -const ConfigTestUtil = require('oae-config/lib/test/util'); -const ContentTestUtil = require('oae-content/lib/test/util'); -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); - -const FoldersContentLibrary = require('oae-folders/lib/internal/contentLibrary'); -const FoldersFolderLibrary = require('oae-folders/lib/internal/foldersLibrary'); -const FoldersLibrary = require('oae-folders/lib/library'); -const FoldersTestUtil = require('oae-folders/lib/test/util'); +import assert from 'assert'; +import fs from 'fs'; +import util from 'util'; +import path from 'path'; +import _ from 'underscore'; + +import * as AuthzAPI from 'oae-authz'; +import * as AuthzTestUtil from 'oae-authz/lib/test/util'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as ContentTestUtil from 'oae-content/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as FoldersContentLibrary from 'oae-folders/lib/internal/contentLibrary'; +import * as FoldersFolderLibrary from 'oae-folders/lib/internal/foldersLibrary'; +import * as FoldersLibrary from 'oae-folders/lib/library'; +import * as FoldersTestUtil from 'oae-folders/lib/test/util'; describe('Folders', () => { let globalAdminRestContext = null; @@ -39,9 +38,9 @@ describe('Folders', () => { let gtAnonymousRestContext = null; /*! - * Set up all the REST contexts for admin and anonymous users with which we - * will invoke requests - */ + * Set up all the REST contexts for admin and anonymous users with which we + * will invoke requests + */ before(callback => { globalAdminRestContext = TestsUtil.createGlobalAdminRestContext(); camAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.cam.host); @@ -52,29 +51,24 @@ describe('Folders', () => { }); /*! - * After each test, ensure the default folder visibility is the default value - */ + * After each test, ensure the default folder visibility is the default value + */ afterEach(callback => { // Ensure the default folder visibility always starts fresh - ConfigTestUtil.clearConfigAndWait( - globalAdminRestContext, - null, - ['oae-folders/visibility/folder'], - err => { - assert.ok(!err); - return callback(); - } - ); + ConfigTestUtil.clearConfigAndWait(globalAdminRestContext, null, ['oae-folders/visibility/folder'], err => { + assert.ok(!err); + return callback(); + }); }); /*! - * Create a member update object whose key is the provided principal id and the value is the - * role change. This is simply a convenience for performing individual role updates on - * folders - * - * @param {String|Object} principalInfo The id of the principal whose role to change, or a principalInfo object. If an object, the `role` key will be added so it can be used in assert test utilities for updating membership - * @param {String|Boolean} roleChange The change to make to the principal's role. Should either be a role (`manager` or `viewer`, or `false` to remove them) - */ + * Create a member update object whose key is the provided principal id and the value is the + * role change. This is simply a convenience for performing individual role updates on + * folders + * + * @param {String|Object} principalInfo The id of the principal whose role to change, or a principalInfo object. If an object, the `role` key will be added so it can be used in assert test utilities for updating membership + * @param {String|Boolean} roleChange The change to make to the principal's role. Should either be a role (`manager` or `viewer`, or `false` to remove them) + */ const _memberUpdate = function(principalInfo, roleChange) { roleChange = _.isUndefined(roleChange) ? 'viewer' : roleChange; @@ -108,661 +102,549 @@ describe('Folders', () => { * Test that verifies creation of a folder */ it('verify folder creation', callback => { - TestsUtil.generateTestUsers( - camAdminRestContext, - 3, - (err, users, mrvisser, stuartf, sathomas) => { - assert.ok(!err); - - FoldersTestUtil.assertCreateFolderSucceeds( - mrvisser.restContext, - 'test displayName', - 'test description', - 'private', - [stuartf], - [sathomas], - createdFolder => { - // Ensure the returned folder model is accurate - assert.ok(createdFolder); - assert.ok(createdFolder.tenant); - assert.strictEqual(createdFolder.tenant.alias, global.oaeTests.tenants.cam.alias); - assert.strictEqual( - createdFolder.tenant.displayName, - global.oaeTests.tenants.cam.displayName - ); - assert.ok(createdFolder.id); - assert.ok(createdFolder.groupId); - assert.strictEqual(createdFolder.displayName, 'test displayName'); - assert.strictEqual(createdFolder.description, 'test description'); - assert.strictEqual(createdFolder.visibility, 'private'); - assert.ok(createdFolder.created); - assert.strictEqual(createdFolder.lastModified, createdFolder.created); - assert.strictEqual( - createdFolder.profilePath, - util.format( - '/folder/%s/%s', - global.oaeTests.tenants.cam.alias, - createdFolder.id.split(':').pop() - ) - ); - assert.strictEqual(createdFolder.resourceType, 'folder'); + TestsUtil.generateTestUsers(camAdminRestContext, 3, (err, users, mrvisser, stuartf, sathomas) => { + assert.ok(!err); - // Sanity check that the folder was created - FoldersTestUtil.assertGetFolderSucceeds( + FoldersTestUtil.assertCreateFolderSucceeds( + mrvisser.restContext, + 'test displayName', + 'test description', + 'private', + [stuartf], + [sathomas], + createdFolder => { + // Ensure the returned folder model is accurate + assert.ok(createdFolder); + assert.ok(createdFolder.tenant); + assert.strictEqual(createdFolder.tenant.alias, global.oaeTests.tenants.cam.alias); + assert.strictEqual(createdFolder.tenant.displayName, global.oaeTests.tenants.cam.displayName); + assert.ok(createdFolder.id); + assert.ok(createdFolder.groupId); + assert.strictEqual(createdFolder.displayName, 'test displayName'); + assert.strictEqual(createdFolder.description, 'test description'); + assert.strictEqual(createdFolder.visibility, 'private'); + assert.ok(createdFolder.created); + assert.strictEqual(createdFolder.lastModified, createdFolder.created); + assert.strictEqual( + createdFolder.profilePath, + util.format('/folder/%s/%s', global.oaeTests.tenants.cam.alias, createdFolder.id.split(':').pop()) + ); + assert.strictEqual(createdFolder.resourceType, 'folder'); + + // Sanity check that the folder was created + FoldersTestUtil.assertGetFolderSucceeds(mrvisser.restContext, createdFolder.id, fetchedFolder => { + // Ensure the fetched folder model is consistent with the created one + assert.ok(fetchedFolder); + assert.ok(fetchedFolder.tenant); + assert.strictEqual(fetchedFolder.tenant.alias, createdFolder.tenant.alias); + assert.strictEqual(fetchedFolder.tenant.displayName, createdFolder.tenant.displayName); + assert.strictEqual(fetchedFolder.id, createdFolder.id); + assert.strictEqual(fetchedFolder.groupId, createdFolder.groupId); + assert.strictEqual(fetchedFolder.displayName, createdFolder.displayName); + assert.strictEqual(fetchedFolder.description, createdFolder.description); + assert.strictEqual(fetchedFolder.visibility, createdFolder.visibility); + assert.strictEqual(fetchedFolder.created, createdFolder.created.toString()); + assert.strictEqual(fetchedFolder.lastModified, createdFolder.lastModified.toString()); + assert.strictEqual(fetchedFolder.profilePath, createdFolder.profilePath); + assert.strictEqual(fetchedFolder.resourceType, createdFolder.resourceType); + + // Ensure createdBy user model is consistent with the user who created it + assert.ok(fetchedFolder.createdBy); + assert.strictEqual(fetchedFolder.createdBy.tenant.alias, mrvisser.user.tenant.alias); + assert.strictEqual(fetchedFolder.createdBy.tenant.displayName, mrvisser.user.tenant.displayName); + assert.strictEqual(fetchedFolder.createdBy.id, mrvisser.user.id); + assert.strictEqual(fetchedFolder.createdBy.displayName, mrvisser.user.displayName); + assert.strictEqual(fetchedFolder.createdBy.visibility, mrvisser.user.visibility); + assert.strictEqual(fetchedFolder.createdBy.email, mrvisser.user.email); + assert.strictEqual(fetchedFolder.createdBy.locale, mrvisser.user.locale); + assert.strictEqual(fetchedFolder.createdBy.timezone, mrvisser.user.timezone); + assert.strictEqual(fetchedFolder.createdBy.publicAlias, mrvisser.user.publicAlias); + assert.strictEqual(fetchedFolder.createdBy.profilePath, mrvisser.user.profilePath); + assert.strictEqual(fetchedFolder.createdBy.resourceType, mrvisser.user.resourceType); + assert.strictEqual(fetchedFolder.createdBy.acceptedTC, mrvisser.user.acceptedTC); + + // Ensure the initial roles are accurate, including the creator being a manager + const expectedRoles = {}; + expectedRoles[stuartf.user.id] = 'manager'; + expectedRoles[sathomas.user.id] = 'viewer'; + expectedRoles[mrvisser.user.id] = 'manager'; + return FoldersTestUtil.assertFullFolderMembersEquals( mrvisser.restContext, createdFolder.id, - fetchedFolder => { - // Ensure the fetched folder model is consistent with the created one - assert.ok(fetchedFolder); - assert.ok(fetchedFolder.tenant); - assert.strictEqual(fetchedFolder.tenant.alias, createdFolder.tenant.alias); - assert.strictEqual( - fetchedFolder.tenant.displayName, - createdFolder.tenant.displayName - ); - assert.strictEqual(fetchedFolder.id, createdFolder.id); - assert.strictEqual(fetchedFolder.groupId, createdFolder.groupId); - assert.strictEqual(fetchedFolder.displayName, createdFolder.displayName); - assert.strictEqual(fetchedFolder.description, createdFolder.description); - assert.strictEqual(fetchedFolder.visibility, createdFolder.visibility); - assert.strictEqual(fetchedFolder.created, createdFolder.created.toString()); - assert.strictEqual( - fetchedFolder.lastModified, - createdFolder.lastModified.toString() - ); - assert.strictEqual(fetchedFolder.profilePath, createdFolder.profilePath); - assert.strictEqual(fetchedFolder.resourceType, createdFolder.resourceType); - - // Ensure createdBy user model is consistent with the user who created it - assert.ok(fetchedFolder.createdBy); - assert.strictEqual( - fetchedFolder.createdBy.tenant.alias, - mrvisser.user.tenant.alias - ); - assert.strictEqual( - fetchedFolder.createdBy.tenant.displayName, - mrvisser.user.tenant.displayName - ); - assert.strictEqual(fetchedFolder.createdBy.id, mrvisser.user.id); - assert.strictEqual( - fetchedFolder.createdBy.displayName, - mrvisser.user.displayName - ); - assert.strictEqual(fetchedFolder.createdBy.visibility, mrvisser.user.visibility); - assert.strictEqual(fetchedFolder.createdBy.email, mrvisser.user.email); - assert.strictEqual(fetchedFolder.createdBy.locale, mrvisser.user.locale); - assert.strictEqual(fetchedFolder.createdBy.timezone, mrvisser.user.timezone); - assert.strictEqual( - fetchedFolder.createdBy.publicAlias, - mrvisser.user.publicAlias - ); - assert.strictEqual( - fetchedFolder.createdBy.profilePath, - mrvisser.user.profilePath - ); - assert.strictEqual( - fetchedFolder.createdBy.resourceType, - mrvisser.user.resourceType - ); - assert.strictEqual(fetchedFolder.createdBy.acceptedTC, mrvisser.user.acceptedTC); - - // Ensure the initial roles are accurate, including the creator being a manager - const expectedRoles = {}; - expectedRoles[stuartf.user.id] = 'manager'; - expectedRoles[sathomas.user.id] = 'viewer'; - expectedRoles[mrvisser.user.id] = 'manager'; - return FoldersTestUtil.assertFullFolderMembersEquals( - mrvisser.restContext, - createdFolder.id, - expectedRoles, - callback - ); - } + expectedRoles, + callback ); - } - ); - } - ); + }); + } + ); + }); }); /** * Test that verifies the validation of creating a folder */ it('verify folder creation validation', callback => { - TestsUtil.generateTestUsers( - camAdminRestContext, - 3, - (err, users, mrvisser, stuartf, sathomas) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 3, (err, users, mrvisser, stuartf, sathomas) => { + assert.ok(!err); - // Ensure displayName is required - FoldersTestUtil.assertCreateFolderFails( - mrvisser.restContext, - '', - 'test description', - 'private', - [stuartf.user.id], - [sathomas.user.id], - 400, - () => { - const longDisplayName = TestsUtil.generateRandomText(83); - assert.ok(longDisplayName.length > 1000); + // Ensure displayName is required + FoldersTestUtil.assertCreateFolderFails( + mrvisser.restContext, + '', + 'test description', + 'private', + [stuartf.user.id], + [sathomas.user.id], + 400, + () => { + const longDisplayName = TestsUtil.generateRandomText(83); + assert.ok(longDisplayName.length > 1000); - // Ensure displayName must be less than 1000 characters - FoldersTestUtil.assertCreateFolderFails( - mrvisser.restContext, - longDisplayName, - 'test description', - 'private', - [stuartf.user.id], - [sathomas.user.id], - 400, - () => { - const longDescription = TestsUtil.generateRandomText(833); - assert.ok(longDescription.length > 10000); + // Ensure displayName must be less than 1000 characters + FoldersTestUtil.assertCreateFolderFails( + mrvisser.restContext, + longDisplayName, + 'test description', + 'private', + [stuartf.user.id], + [sathomas.user.id], + 400, + () => { + const longDescription = TestsUtil.generateRandomText(833); + assert.ok(longDescription.length > 10000); - // Ensure description must be less than 10000 characters - FoldersTestUtil.assertCreateFolderFails( - mrvisser.restContext, - 'test displayName', - longDescription, - 'private', - [stuartf.user.id], - [sathomas.user.id], - 400, - () => { - // Ensure visibility must be valid - FoldersTestUtil.assertCreateFolderFails( - mrvisser.restContext, - 'test displayName', - 'test description', - 'notvalid', - [stuartf.user.id], - [sathomas.user.id], - 400, - () => { - // Ensure manager id must be a valid resource id - FoldersTestUtil.assertCreateFolderFails( - mrvisser.restContext, - 'test displayName', - 'test description', - 'private', - ['notaresourceid'], - [sathomas.user.id], - 400, - () => { - // Ensure manager id must be a principal id - FoldersTestUtil.assertCreateFolderFails( - mrvisser.restContext, - 'test displayName', - 'test description', - 'private', - ['c:oaetest:contentid'], - [sathomas.user.id], - 400, - () => { - // Ensure manager id must be an existing principal id - FoldersTestUtil.assertCreateFolderFails( - mrvisser.restContext, - 'test displayName', - 'test description', - 'private', - ['u:oaetest:nonexistinguserid'], - [sathomas.user.id], - 400, - () => { - FoldersTestUtil.assertCreateFolderFails( - mrvisser.restContext, - 'test displayName', - 'test description', - 'private', - ['g:oaetest:nonexistinggroupid'], - [sathomas.user.id], - 400, - () => { - // Ensure viewer id must be a valid resource id - FoldersTestUtil.assertCreateFolderFails( - mrvisser.restContext, - 'test displayName', - 'test description', - 'private', - [stuartf.user.id], - ['notaresourceid'], - 400, - () => { - // Ensure viewer id must be a principal id - FoldersTestUtil.assertCreateFolderFails( - mrvisser.restContext, - 'test displayName', - 'test description', - 'private', - [stuartf.user.id], - ['c:oaetest:contentid'], - 400, - () => { - // Ensure viewer id must be an existing principal id - FoldersTestUtil.assertCreateFolderFails( - mrvisser.restContext, - 'test displayName', - 'test description', - 'private', - [stuartf.user.id], - ['u:oaetest:nonexistinguserid'], - 400, - () => { - FoldersTestUtil.assertCreateFolderFails( - mrvisser.restContext, - 'test displayName', - 'test description', - 'private', - [stuartf.user.id], - ['g:oaetest:nonexistinggroupid'], - 400, - () => { - // Sanity check that creating a folder works with base input - FoldersTestUtil.assertCreateFolderSucceeds( - mrvisser.restContext, - 'test displayName', - 'test description', - 'private', - [stuartf], - [sathomas], - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + // Ensure description must be less than 10000 characters + FoldersTestUtil.assertCreateFolderFails( + mrvisser.restContext, + 'test displayName', + longDescription, + 'private', + [stuartf.user.id], + [sathomas.user.id], + 400, + () => { + // Ensure visibility must be valid + FoldersTestUtil.assertCreateFolderFails( + mrvisser.restContext, + 'test displayName', + 'test description', + 'notvalid', + [stuartf.user.id], + [sathomas.user.id], + 400, + () => { + // Ensure manager id must be a valid resource id + FoldersTestUtil.assertCreateFolderFails( + mrvisser.restContext, + 'test displayName', + 'test description', + 'private', + ['notaresourceid'], + [sathomas.user.id], + 400, + () => { + // Ensure manager id must be a principal id + FoldersTestUtil.assertCreateFolderFails( + mrvisser.restContext, + 'test displayName', + 'test description', + 'private', + ['c:oaetest:contentid'], + [sathomas.user.id], + 400, + () => { + // Ensure manager id must be an existing principal id + FoldersTestUtil.assertCreateFolderFails( + mrvisser.restContext, + 'test displayName', + 'test description', + 'private', + ['u:oaetest:nonexistinguserid'], + [sathomas.user.id], + 400, + () => { + FoldersTestUtil.assertCreateFolderFails( + mrvisser.restContext, + 'test displayName', + 'test description', + 'private', + ['g:oaetest:nonexistinggroupid'], + [sathomas.user.id], + 400, + () => { + // Ensure viewer id must be a valid resource id + FoldersTestUtil.assertCreateFolderFails( + mrvisser.restContext, + 'test displayName', + 'test description', + 'private', + [stuartf.user.id], + ['notaresourceid'], + 400, + () => { + // Ensure viewer id must be a principal id + FoldersTestUtil.assertCreateFolderFails( + mrvisser.restContext, + 'test displayName', + 'test description', + 'private', + [stuartf.user.id], + ['c:oaetest:contentid'], + 400, + () => { + // Ensure viewer id must be an existing principal id + FoldersTestUtil.assertCreateFolderFails( + mrvisser.restContext, + 'test displayName', + 'test description', + 'private', + [stuartf.user.id], + ['u:oaetest:nonexistinguserid'], + 400, + () => { + FoldersTestUtil.assertCreateFolderFails( + mrvisser.restContext, + 'test displayName', + 'test description', + 'private', + [stuartf.user.id], + ['g:oaetest:nonexistinggroupid'], + 400, + () => { + // Sanity check that creating a folder works with base input + FoldersTestUtil.assertCreateFolderSucceeds( + mrvisser.restContext, + 'test displayName', + 'test description', + 'private', + [stuartf], + [sathomas], + () => { + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); }); /** * Test that verifies the authorization of creating a folder and associating it with users */ it('verify folder creation authorization', callback => { - FoldersTestUtil.setupMultiTenantPrivacyEntities( - (publicTenant, publicTenant1, privateTenant, privateTenant1) => { - // Ensure an anonymous user cannot create a folder - FoldersTestUtil.assertCreateFolderFails( - camAnonymousRestContext, - 'test', - 'test', - 'private', - null, - null, - 401, - () => { - // Ensure a user cannot create a folder with a private user from the same tenant as a viewer - FoldersTestUtil.assertCreateFolderSucceeds( - publicTenant.loggedinUser.restContext, - 'test', - 'test', - 'private', - [publicTenant.publicUser], - null, - () => { - FoldersTestUtil.assertCreateFolderSucceeds( - publicTenant.publicUser.restContext, - 'test', - 'test', - 'private', - [publicTenant.loggedinUser], - null, - () => { - FoldersTestUtil.assertCreateFolderFails( - publicTenant.publicUser.restContext, - 'test', - 'test', - 'private', - [publicTenant.privateUser.user.id], - null, - 401, - () => { - // Ensure a user cannot create a folder with a loggedin or private user from another tenant as a viewer - FoldersTestUtil.assertCreateFolderSucceeds( - publicTenant.publicUser.restContext, - 'test', - 'test', - 'private', - [publicTenant1.publicUser], - null, - () => { - FoldersTestUtil.assertCreateFolderFails( - publicTenant.publicUser.restContext, - 'test', - 'test', - 'private', - [publicTenant1.loggedinUser.user.id], - null, - 401, - () => { - FoldersTestUtil.assertCreateFolderFails( - publicTenant.publicUser.restContext, - 'test', - 'test', - 'private', - [publicTenant1.privateUser.user.id], - null, - 401, - () => { - // Ensure a user cannot create a folder with any user from a private tenant as a viewer - FoldersTestUtil.assertCreateFolderFails( - publicTenant.publicUser.restContext, - 'test', - 'test', - 'private', - [privateTenant.publicUser.user.id], - null, - 401, - () => { - FoldersTestUtil.assertCreateFolderFails( - publicTenant.publicUser.restContext, - 'test', - 'test', - 'private', - [privateTenant.loggedinUser.user.id], - null, - 401, - () => { - FoldersTestUtil.assertCreateFolderFails( - publicTenant.publicUser.restContext, - 'test', - 'test', - 'private', - [privateTenant.privateUser.user.id], - null, - 401, - () => { - // Ensure a user from a private tenant cannot create a folder with any outside user - FoldersTestUtil.assertCreateFolderFails( - privateTenant.publicUser.restContext, - 'test', - 'test', - 'private', - [publicTenant.publicUser.user.id], - null, - 401, - () => { - FoldersTestUtil.assertCreateFolderFails( - privateTenant.publicUser.restContext, - 'test', - 'test', - 'private', - [publicTenant.loggedinUser.user.id], - null, - 401, - () => { - FoldersTestUtil.assertCreateFolderFails( - privateTenant.publicUser.restContext, - 'test', - 'test', - 'private', - [publicTenant.privateUser.user.id], - null, - 401, - () => { - // Ensure an admin can create a folder with a private user from the same tenant as a viewer - FoldersTestUtil.assertCreateFolderSucceeds( - publicTenant.adminRestContext, - 'test', - 'test', - 'private', - [publicTenant.publicUser], - null, - () => { - FoldersTestUtil.assertCreateFolderSucceeds( - publicTenant.adminRestContext, - 'test', - 'test', - 'private', - [publicTenant.loggedinUser], - null, - () => { - FoldersTestUtil.assertCreateFolderSucceeds( - publicTenant.adminRestContext, - 'test', - 'test', - 'private', - [publicTenant.privateUser], - null, - () => { - // Ensure an admin cannot create a folder with a loggedin or private user from another tenant as a viewer - FoldersTestUtil.assertCreateFolderSucceeds( - publicTenant.adminRestContext, - 'test', - 'test', - 'private', - [ - publicTenant1.publicUser - ], - null, - () => { - FoldersTestUtil.assertCreateFolderFails( - publicTenant.adminRestContext, - 'test', - 'test', - 'private', - [ - publicTenant1 - .loggedinUser - .user.id - ], - null, - 401, - () => { - FoldersTestUtil.assertCreateFolderFails( - publicTenant.adminRestContext, - 'test', - 'test', - 'private', - [ - publicTenant1 - .privateUser - .user.id - ], - null, - 401, - () => { - // Ensure an admin cannot create a folder with any user from a private tenant as a viewer - FoldersTestUtil.assertCreateFolderFails( - publicTenant.adminRestContext, - 'test', - 'test', - 'private', - [ - privateTenant - .publicUser - .user.id - ], - null, - 401, - () => { - FoldersTestUtil.assertCreateFolderFails( - publicTenant.adminRestContext, - 'test', - 'test', - 'private', - [ - privateTenant - .loggedinUser - .user - .id - ], - null, - 401, - () => { - FoldersTestUtil.assertCreateFolderFails( - publicTenant.adminRestContext, - 'test', - 'test', - 'private', - [ - privateTenant - .privateUser - .user - .id - ], - null, - 401, - () => { - // Ensure an admin from a private tenant cannot create a folder with any outside user - FoldersTestUtil.assertCreateFolderFails( - privateTenant.adminRestContext, - 'test', - 'test', - 'private', - [ - publicTenant - .publicUser - .user - .id - ], - null, - 401, - () => { - FoldersTestUtil.assertCreateFolderFails( - privateTenant.adminRestContext, - 'test', - 'test', - 'private', - [ - publicTenant - .loggedinUser - .user - .id - ], - null, - 401, - () => { - return FoldersTestUtil.assertCreateFolderFails( - privateTenant.adminRestContext, - 'test', - 'test', - 'private', - [ - publicTenant - .privateUser - .user - .id - ], - null, - 401, - callback - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - }); - - /** - * Test that verifies the visibility of a folder defaults to the tenant configuration - */ - it('verify folder visibility defaults to the configured tenant default', callback => { - TestsUtil.generateTestUsers(camAdminRestContext, 1, (err, users, mrvisser) => { - assert.ok(!err); - - // Ensure a folder created without a visibility defaults to public - RestAPI.Folders.createFolder( - mrvisser.restContext, + FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1, privateTenant, privateTenant1) => { + // Ensure an anonymous user cannot create a folder + FoldersTestUtil.assertCreateFolderFails( + camAnonymousRestContext, 'test', 'test', + 'private', null, null, - null, - (err, createdFolder) => { - assert.ok(!err); - assert.strictEqual(createdFolder.visibility, 'public'); - - // Set the default privacy to private - ConfigTestUtil.updateConfigAndWait( - globalAdminRestContext, + 401, + () => { + // Ensure a user cannot create a folder with a private user from the same tenant as a viewer + FoldersTestUtil.assertCreateFolderSucceeds( + publicTenant.loggedinUser.restContext, + 'test', + 'test', + 'private', + [publicTenant.publicUser], null, - { 'oae-folders/visibility/folder': 'private' }, - err => { - assert.ok(!err); - - // Ensure a folder created without a visibility now defaults to private - RestAPI.Folders.createFolder( - mrvisser.restContext, + () => { + FoldersTestUtil.assertCreateFolderSucceeds( + publicTenant.publicUser.restContext, 'test', 'test', + 'private', + [publicTenant.loggedinUser], null, - null, - null, - (err, createdFolder) => { - assert.ok(!err); - assert.strictEqual(createdFolder.visibility, 'private'); - return callback(); + () => { + FoldersTestUtil.assertCreateFolderFails( + publicTenant.publicUser.restContext, + 'test', + 'test', + 'private', + [publicTenant.privateUser.user.id], + null, + 401, + () => { + // Ensure a user cannot create a folder with a loggedin or private user from another tenant as a viewer + FoldersTestUtil.assertCreateFolderSucceeds( + publicTenant.publicUser.restContext, + 'test', + 'test', + 'private', + [publicTenant1.publicUser], + null, + () => { + FoldersTestUtil.assertCreateFolderFails( + publicTenant.publicUser.restContext, + 'test', + 'test', + 'private', + [publicTenant1.loggedinUser.user.id], + null, + 401, + () => { + FoldersTestUtil.assertCreateFolderFails( + publicTenant.publicUser.restContext, + 'test', + 'test', + 'private', + [publicTenant1.privateUser.user.id], + null, + 401, + () => { + // Ensure a user cannot create a folder with any user from a private tenant as a viewer + FoldersTestUtil.assertCreateFolderFails( + publicTenant.publicUser.restContext, + 'test', + 'test', + 'private', + [privateTenant.publicUser.user.id], + null, + 401, + () => { + FoldersTestUtil.assertCreateFolderFails( + publicTenant.publicUser.restContext, + 'test', + 'test', + 'private', + [privateTenant.loggedinUser.user.id], + null, + 401, + () => { + FoldersTestUtil.assertCreateFolderFails( + publicTenant.publicUser.restContext, + 'test', + 'test', + 'private', + [privateTenant.privateUser.user.id], + null, + 401, + () => { + // Ensure a user from a private tenant cannot create a folder with any outside user + FoldersTestUtil.assertCreateFolderFails( + privateTenant.publicUser.restContext, + 'test', + 'test', + 'private', + [publicTenant.publicUser.user.id], + null, + 401, + () => { + FoldersTestUtil.assertCreateFolderFails( + privateTenant.publicUser.restContext, + 'test', + 'test', + 'private', + [publicTenant.loggedinUser.user.id], + null, + 401, + () => { + FoldersTestUtil.assertCreateFolderFails( + privateTenant.publicUser.restContext, + 'test', + 'test', + 'private', + [publicTenant.privateUser.user.id], + null, + 401, + () => { + // Ensure an admin can create a folder with a private user from the same tenant as a viewer + FoldersTestUtil.assertCreateFolderSucceeds( + publicTenant.adminRestContext, + 'test', + 'test', + 'private', + [publicTenant.publicUser], + null, + () => { + FoldersTestUtil.assertCreateFolderSucceeds( + publicTenant.adminRestContext, + 'test', + 'test', + 'private', + [publicTenant.loggedinUser], + null, + () => { + FoldersTestUtil.assertCreateFolderSucceeds( + publicTenant.adminRestContext, + 'test', + 'test', + 'private', + [publicTenant.privateUser], + null, + () => { + // Ensure an admin cannot create a folder with a loggedin or private user from another tenant as a viewer + FoldersTestUtil.assertCreateFolderSucceeds( + publicTenant.adminRestContext, + 'test', + 'test', + 'private', + [publicTenant1.publicUser], + null, + () => { + FoldersTestUtil.assertCreateFolderFails( + publicTenant.adminRestContext, + 'test', + 'test', + 'private', + [publicTenant1.loggedinUser.user.id], + null, + 401, + () => { + FoldersTestUtil.assertCreateFolderFails( + publicTenant.adminRestContext, + 'test', + 'test', + 'private', + [publicTenant1.privateUser.user.id], + null, + 401, + () => { + // Ensure an admin cannot create a folder with any user from a private tenant as a viewer + FoldersTestUtil.assertCreateFolderFails( + publicTenant.adminRestContext, + 'test', + 'test', + 'private', + [ + privateTenant.publicUser.user.id + ], + null, + 401, + () => { + FoldersTestUtil.assertCreateFolderFails( + publicTenant.adminRestContext, + 'test', + 'test', + 'private', + [ + privateTenant.loggedinUser + .user.id + ], + null, + 401, + () => { + FoldersTestUtil.assertCreateFolderFails( + publicTenant.adminRestContext, + 'test', + 'test', + 'private', + [ + privateTenant + .privateUser.user.id + ], + null, + 401, + () => { + // Ensure an admin from a private tenant cannot create a folder with any outside user + FoldersTestUtil.assertCreateFolderFails( + privateTenant.adminRestContext, + 'test', + 'test', + 'private', + [ + publicTenant + .publicUser.user + .id + ], + null, + 401, + () => { + FoldersTestUtil.assertCreateFolderFails( + privateTenant.adminRestContext, + 'test', + 'test', + 'private', + [ + publicTenant + .loggedinUser + .user.id + ], + null, + 401, + () => { + return FoldersTestUtil.assertCreateFolderFails( + privateTenant.adminRestContext, + 'test', + 'test', + 'private', + [ + publicTenant + .privateUser + .user.id + ], + null, + 401, + callback + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); } ); } @@ -772,6 +654,46 @@ describe('Folders', () => { }); }); + /** + * Test that verifies the visibility of a folder defaults to the tenant configuration + */ + it('verify folder visibility defaults to the configured tenant default', callback => { + TestsUtil.generateTestUsers(camAdminRestContext, 1, (err, users, mrvisser) => { + assert.ok(!err); + + // Ensure a folder created without a visibility defaults to public + RestAPI.Folders.createFolder(mrvisser.restContext, 'test', 'test', null, null, null, (err, createdFolder) => { + assert.ok(!err); + assert.strictEqual(createdFolder.visibility, 'public'); + + // Set the default privacy to private + ConfigTestUtil.updateConfigAndWait( + globalAdminRestContext, + null, + { 'oae-folders/visibility/folder': 'private' }, + err => { + assert.ok(!err); + + // Ensure a folder created without a visibility now defaults to private + RestAPI.Folders.createFolder( + mrvisser.restContext, + 'test', + 'test', + null, + null, + null, + (err, createdFolder) => { + assert.ok(!err); + assert.strictEqual(createdFolder.visibility, 'private'); + return callback(); + } + ); + } + ); + }); + }); + }); + /** * Create a folder with a given set of managers and viewers. Then check if * the user who created the folder was given explicit manager rights. @@ -783,27 +705,20 @@ describe('Folders', () => { * @param {Function} callback Standard callback function */ const createAndVerifyManager = function(user, managers, viewers, expectedManager, callback) { - RestAPI.Folders.createFolder( - user.restContext, - 'test', - 'test', - null, - managers, - viewers, - (err, folder) => { - assert.ok(!err); + RestAPI.Folders.createFolder(user.restContext, 'test', 'test', null, managers, viewers, (err, folder) => { + assert.ok(!err); - FoldersTestUtil.getAllFolderMembers(user.restContext, folder.id, null, members => { - if (expectedManager) { - const member = _.find(members, member => { - return member.profile.id === user.user.id && member.role === 'manager'; - }); - assert.ok(member); - } - return callback(); - }); - } - ); + FoldersTestUtil.getAllFolderMembers(user.restContext, folder.id, null, members => { + if (expectedManager) { + const member = _.find(members, member => { + return member.profile.id === user.user.id && member.role === 'manager'; + }); + assert.ok(member); + } + + return callback(); + }); + }); }; /** @@ -828,89 +743,48 @@ describe('Folders', () => { 'manager', () => { const roleChange = AuthzTestUtil.createRoleChange([stuart.user.id], 'member'); - RestAPI.Group.setGroupMembers( - nico.restContext, - nicoGroup3.group.id, - roleChange, - err => { + RestAPI.Group.setGroupMembers(nico.restContext, nicoGroup3.group.id, roleChange, err => { + assert.ok(!err); + const roleChange = AuthzTestUtil.createRoleChange([stuart.user.id], 'manager'); + RestAPI.Group.setGroupMembers(nico.restContext, nicoGroup6.group.id, roleChange, err => { assert.ok(!err); - const roleChange = AuthzTestUtil.createRoleChange( - [stuart.user.id], - 'manager' - ); - RestAPI.Group.setGroupMembers( - nico.restContext, - nicoGroup6.group.id, - roleChange, - err => { - assert.ok(!err); - createAndVerifyManager(simong, null, null, true, () => { - createAndVerifyManager(simong, [nico.user.id], null, true, () => { + createAndVerifyManager(simong, null, null, true, () => { + createAndVerifyManager(simong, [nico.user.id], null, true, () => { + createAndVerifyManager(simong, null, [simongGroup.group.id], true, () => { + createAndVerifyManager(simong, [simongGroup.group.id], null, false, () => { createAndVerifyManager( simong, + [simongGroup.group.id, nicoGroup1.group.id], null, - [simongGroup.group.id], - true, + false, () => { + // Verify a mix of users and groups that Simon can't manage createAndVerifyManager( simong, - [simongGroup.group.id], + [nico.user.id, simongGroup.group.id, nicoGroup1.group.id], null, false, () => { - createAndVerifyManager( - simong, - [simongGroup.group.id, nicoGroup1.group.id], - null, - false, - () => { - // Verify a mix of users and groups that Simon can't manage - createAndVerifyManager( - simong, - [ - nico.user.id, - simongGroup.group.id, - nicoGroup1.group.id - ], - null, - false, - () => { - // Stuart is an indirect member of group 1, he should be - // made a manager of the folder as he cannot manage nicoGroup1 - createAndVerifyManager( - stuart, - [nicoGroup1.group.id], - null, - true, - () => { - // Stuart is an indirect manager of group 4, he should not be made - // a manager of the folder as he can manage nicoGroup4 (indirectly) - createAndVerifyManager( - stuart, - [nicoGroup4.group.id], - null, - false, - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); + // Stuart is an indirect member of group 1, he should be + // made a manager of the folder as he cannot manage nicoGroup1 + createAndVerifyManager(stuart, [nicoGroup1.group.id], null, true, () => { + // Stuart is an indirect manager of group 4, he should not be made + // a manager of the folder as he can manage nicoGroup4 (indirectly) + createAndVerifyManager(stuart, [nicoGroup4.group.id], null, false, () => { + return callback(); + }); + }); } ); } ); }); }); - } - ); - } - ); + }); + }); + }); + }); } ); } @@ -954,87 +828,72 @@ describe('Folders', () => { 404, () => { // Missing update values - FoldersTestUtil.assertUpdateFolderFails( - simong.restContext, - folder.id, - null, - 400, - () => { - // Unused update values - FoldersTestUtil.assertUpdateFolderFails( - simong.restContext, - folder.id, - { not: 'right' }, - 400, - () => { - // Invalid displayName - const longDisplayName = TestsUtil.generateRandomText(83); - FoldersTestUtil.assertUpdateFolderFails( - simong.restContext, - folder.id, - { displayName: longDisplayName }, - 400, - () => { - // Invalid description - const longDescription = TestsUtil.generateRandomText(833); - FoldersTestUtil.assertUpdateFolderFails( - simong.restContext, - folder.id, - { description: longDescription }, - 400, - () => { - // Invalid visibility - FoldersTestUtil.assertUpdateFolderFails( - simong.restContext, - folder.id, - { visibility: 'noowpe' }, - 400, - () => { - // Sanity check nothing changed - FoldersTestUtil.assertGetFolderSucceeds( - simong.restContext, - folder.id, - checkFolder => { - assert.strictEqual( - checkFolder.displayName, - folder.displayName - ); - assert.strictEqual( - checkFolder.description, - folder.description - ); - assert.strictEqual( - checkFolder.visibility, - folder.visibility - ); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - }); - }); - - /** - * Test that verifies only managers can update a folder - */ - it('verify update folder authorization', callback => { - TestsUtil.generateTestUsers(camAdminRestContext, 3, (err, users, simong, nico, bert) => { - assert.ok(!err); + FoldersTestUtil.assertUpdateFolderFails(simong.restContext, folder.id, null, 400, () => { + // Unused update values + FoldersTestUtil.assertUpdateFolderFails( + simong.restContext, + folder.id, + { not: 'right' }, + 400, + () => { + // Invalid displayName + const longDisplayName = TestsUtil.generateRandomText(83); + FoldersTestUtil.assertUpdateFolderFails( + simong.restContext, + folder.id, + { displayName: longDisplayName }, + 400, + () => { + // Invalid description + const longDescription = TestsUtil.generateRandomText(833); + FoldersTestUtil.assertUpdateFolderFails( + simong.restContext, + folder.id, + { description: longDescription }, + 400, + () => { + // Invalid visibility + FoldersTestUtil.assertUpdateFolderFails( + simong.restContext, + folder.id, + { visibility: 'noowpe' }, + 400, + () => { + // Sanity check nothing changed + FoldersTestUtil.assertGetFolderSucceeds( + simong.restContext, + folder.id, + checkFolder => { + assert.strictEqual(checkFolder.displayName, folder.displayName); + assert.strictEqual(checkFolder.description, folder.description); + assert.strictEqual(checkFolder.visibility, folder.visibility); + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + }); + } + ); + } + ); + } + ); + }); + }); + + /** + * Test that verifies only managers can update a folder + */ + it('verify update folder authorization', callback => { + TestsUtil.generateTestUsers(camAdminRestContext, 3, (err, users, simong, nico, bert) => { + assert.ok(!err); // Create a folder and make Nico a member of it FoldersTestUtil.assertCreateFolderSucceeds( @@ -1074,14 +933,10 @@ describe('Folders', () => { 401, () => { // Sanity check the folder was not updated - FoldersTestUtil.assertGetFolderSucceeds( - simong.restContext, - folder.id, - checkFolder => { - assert.strictEqual(folder.visibility, checkFolder.visibility); - return callback(); - } - ); + FoldersTestUtil.assertGetFolderSucceeds(simong.restContext, folder.id, checkFolder => { + assert.strictEqual(folder.visibility, checkFolder.visibility); + return callback(); + }); } ); } @@ -1140,14 +995,10 @@ describe('Folders', () => { 401, () => { // Sanity check the folder was not updated - FoldersTestUtil.assertGetFolderSucceeds( - simong.restContext, - folder.id, - checkFolder => { - assert.strictEqual(folder.visibility, checkFolder.visibility); - return callback(); - } - ); + FoldersTestUtil.assertGetFolderSucceeds(simong.restContext, folder.id, checkFolder => { + assert.strictEqual(folder.visibility, checkFolder.visibility); + return callback(); + }); } ); } @@ -1256,10 +1107,7 @@ describe('Folders', () => { null, null, result => { - assert.strictEqual( - result.results.length, - 1 - ); + assert.strictEqual(result.results.length, 1); return callback(); } @@ -1385,68 +1233,55 @@ describe('Folders', () => { () => { // Change the visibility of the folder to loggedin AND change the content items const updates = { visibility: 'loggedin' }; - RestAPI.Folders.updateFolder( - simong.restContext, - folder.id, - updates, - (err, folder) => { - assert.ok(!err); - assert.ok(folder); - - // Sanity-check the full folder profile was returned - assert.strictEqual(folder.canShare, true); - assert.strictEqual(folder.canManage, true); - assert.strictEqual(folder.canAddItem, true); - assert.ok(_.isObject(folder.createdBy)); - assert.strictEqual(folder.createdBy.id, simong.user.id); - - // Update the content items in the folder - RestAPI.Folders.updateFolderContentVisibility( - simong.restContext, - folder.id, - 'loggedin', - (err, data) => { - assert.ok(!err); + RestAPI.Folders.updateFolder(simong.restContext, folder.id, updates, (err, folder) => { + assert.ok(!err); + assert.ok(folder); + + // Sanity-check the full folder profile was returned + assert.strictEqual(folder.canShare, true); + assert.strictEqual(folder.canManage, true); + assert.strictEqual(folder.canAddItem, true); + assert.ok(_.isObject(folder.createdBy)); + assert.strictEqual(folder.createdBy.id, simong.user.id); + + // Update the content items in the folder + RestAPI.Folders.updateFolderContentVisibility( + simong.restContext, + folder.id, + 'loggedin', + (err, data) => { + assert.ok(!err); - // Only 1 item should've failed - assert.strictEqual(data.failedContent.length, 1); - assert.strictEqual(data.failedContent[0].id, nicosLink.id); + // Only 1 item should've failed + assert.strictEqual(data.failedContent.length, 1); + assert.strictEqual(data.failedContent[0].id, nicosLink.id); - // The failed content items should have a signature - assert.ok(data.failedContent[0].signature); + // The failed content items should have a signature + assert.ok(data.failedContent[0].signature); - // Assert that simonsLink's visibility changed - RestAPI.Content.getContent( - simong.restContext, - simonsLink.id, - (err, content) => { - assert.ok(!err); - assert.strictEqual(content.visibility, 'loggedin'); + // Assert that simonsLink's visibility changed + RestAPI.Content.getContent(simong.restContext, simonsLink.id, (err, content) => { + assert.ok(!err); + assert.strictEqual(content.visibility, 'loggedin'); - // Assert that nicosLink's visibility did not change - RestAPI.Content.getContent( - simong.restContext, - nicosLink.id, - (err, content) => { - assert.ok(!err); - assert.strictEqual(content.visibility, 'public'); + // Assert that nicosLink's visibility did not change + RestAPI.Content.getContent(simong.restContext, nicosLink.id, (err, content) => { + assert.ok(!err); + assert.strictEqual(content.visibility, 'public'); - FoldersTestUtil.assertFolderEquals( - nico.restContext, - folder.id, - [simonsLink.id, nicosLink.id], - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + FoldersTestUtil.assertFolderEquals( + nico.restContext, + folder.id, + [simonsLink.id, nicosLink.id], + () => { + return callback(); + } + ); + }); + }); + } + ); + }); } ); } @@ -1470,17 +1305,12 @@ describe('Folders', () => { // Ensure fetching using an invalid id results in an error FoldersTestUtil.assertGetFolderFails(mrvisser.restContext, 'invalidid', 400, () => { // Ensure fetching using a non-existing id results in a 404 - FoldersTestUtil.assertGetFolderFails( - mrvisser.restContext, - 'x:oaetest:nonexistingid', - 404, - () => { - // Sanity check getting an existing folder - FoldersTestUtil.assertGetFolderSucceeds(mrvisser.restContext, folder.id, () => { - return callback(); - }); - } - ); + FoldersTestUtil.assertGetFolderFails(mrvisser.restContext, 'x:oaetest:nonexistingid', 404, () => { + // Sanity check getting an existing folder + FoldersTestUtil.assertGetFolderSucceeds(mrvisser.restContext, folder.id, () => { + return callback(); + }); + }); }); }); }); @@ -1602,51 +1432,21 @@ describe('Folders', () => { createdFolder.id, fetchedFolder => { assert.ok(fetchedFolder.createdBy); - assert.strictEqual( - fetchedFolder.createdBy.tenant.alias, - publicTenant.privateUser.user.tenant.alias - ); + assert.strictEqual(fetchedFolder.createdBy.tenant.alias, publicTenant.privateUser.user.tenant.alias); assert.strictEqual( fetchedFolder.createdBy.tenant.displayName, publicTenant.privateUser.user.tenant.displayName ); assert.strictEqual(fetchedFolder.createdBy.id, publicTenant.privateUser.user.id); - assert.strictEqual( - fetchedFolder.createdBy.displayName, - publicTenant.privateUser.user.displayName - ); - assert.strictEqual( - fetchedFolder.createdBy.visibility, - publicTenant.privateUser.user.visibility - ); - assert.strictEqual( - fetchedFolder.createdBy.email, - publicTenant.privateUser.user.email - ); - assert.strictEqual( - fetchedFolder.createdBy.locale, - publicTenant.privateUser.user.locale - ); - assert.strictEqual( - fetchedFolder.createdBy.timezone, - publicTenant.privateUser.user.timezone - ); - assert.strictEqual( - fetchedFolder.createdBy.publicAlias, - publicTenant.privateUser.user.publicAlias - ); - assert.strictEqual( - fetchedFolder.createdBy.profilePath, - publicTenant.privateUser.user.profilePath - ); - assert.strictEqual( - fetchedFolder.createdBy.resourceType, - publicTenant.privateUser.user.resourceType - ); - assert.strictEqual( - fetchedFolder.createdBy.acceptedTC, - publicTenant.privateUser.user.acceptedTC - ); + assert.strictEqual(fetchedFolder.createdBy.displayName, publicTenant.privateUser.user.displayName); + assert.strictEqual(fetchedFolder.createdBy.visibility, publicTenant.privateUser.user.visibility); + assert.strictEqual(fetchedFolder.createdBy.email, publicTenant.privateUser.user.email); + assert.strictEqual(fetchedFolder.createdBy.locale, publicTenant.privateUser.user.locale); + assert.strictEqual(fetchedFolder.createdBy.timezone, publicTenant.privateUser.user.timezone); + assert.strictEqual(fetchedFolder.createdBy.publicAlias, publicTenant.privateUser.user.publicAlias); + assert.strictEqual(fetchedFolder.createdBy.profilePath, publicTenant.privateUser.user.profilePath); + assert.strictEqual(fetchedFolder.createdBy.resourceType, publicTenant.privateUser.user.resourceType); + assert.strictEqual(fetchedFolder.createdBy.acceptedTC, publicTenant.privateUser.user.acceptedTC); // Ensure an admin user gets the full creator profile when they get the folder FoldersTestUtil.assertGetFolderSucceeds( @@ -1662,46 +1462,19 @@ describe('Folders', () => { fetchedFolder.createdBy.tenant.displayName, publicTenant.privateUser.user.tenant.displayName ); - assert.strictEqual( - fetchedFolder.createdBy.id, - publicTenant.privateUser.user.id - ); - assert.strictEqual( - fetchedFolder.createdBy.displayName, - publicTenant.privateUser.user.displayName - ); - assert.strictEqual( - fetchedFolder.createdBy.visibility, - publicTenant.privateUser.user.visibility - ); - assert.strictEqual( - fetchedFolder.createdBy.email, - publicTenant.privateUser.user.email - ); - assert.strictEqual( - fetchedFolder.createdBy.locale, - publicTenant.privateUser.user.locale - ); - assert.strictEqual( - fetchedFolder.createdBy.timezone, - publicTenant.privateUser.user.timezone - ); - assert.strictEqual( - fetchedFolder.createdBy.publicAlias, - publicTenant.privateUser.user.publicAlias - ); - assert.strictEqual( - fetchedFolder.createdBy.profilePath, - publicTenant.privateUser.user.profilePath - ); + assert.strictEqual(fetchedFolder.createdBy.id, publicTenant.privateUser.user.id); + assert.strictEqual(fetchedFolder.createdBy.displayName, publicTenant.privateUser.user.displayName); + assert.strictEqual(fetchedFolder.createdBy.visibility, publicTenant.privateUser.user.visibility); + assert.strictEqual(fetchedFolder.createdBy.email, publicTenant.privateUser.user.email); + assert.strictEqual(fetchedFolder.createdBy.locale, publicTenant.privateUser.user.locale); + assert.strictEqual(fetchedFolder.createdBy.timezone, publicTenant.privateUser.user.timezone); + assert.strictEqual(fetchedFolder.createdBy.publicAlias, publicTenant.privateUser.user.publicAlias); + assert.strictEqual(fetchedFolder.createdBy.profilePath, publicTenant.privateUser.user.profilePath); assert.strictEqual( fetchedFolder.createdBy.resourceType, publicTenant.privateUser.user.resourceType ); - assert.strictEqual( - fetchedFolder.createdBy.acceptedTC, - publicTenant.privateUser.user.acceptedTC - ); + assert.strictEqual(fetchedFolder.createdBy.acceptedTC, publicTenant.privateUser.user.acceptedTC); // Ensure another user from the tenant gets a scrubbed creator profile when they get the folder FoldersTestUtil.assertGetFolderSucceeds( @@ -1717,10 +1490,7 @@ describe('Folders', () => { fetchedFolder.createdBy.tenant.displayName, publicTenant.privateUser.user.tenant.displayName ); - assert.strictEqual( - fetchedFolder.createdBy.id, - publicTenant.privateUser.user.id - ); + assert.strictEqual(fetchedFolder.createdBy.id, publicTenant.privateUser.user.id); assert.strictEqual( fetchedFolder.createdBy.displayName, publicTenant.privateUser.user.publicAlias @@ -1755,75 +1525,69 @@ describe('Folders', () => { * Test that verifies the permission flags (e.g., `canShare`, `canAddItem`) of a full folder profile */ it('verify get folder profile permission flags', callback => { - FoldersTestUtil.setupMultiTenantPrivacyEntities( - (publicTenant, publicTenant1, privateTenant) => { - // Create one more folder as the public user - FoldersTestUtil.generateTestFolders( - publicTenant.publicUser.restContext, - 1, - createdFolder => { - // Ensure permission flags for admin + FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1, privateTenant) => { + // Create one more folder as the public user + FoldersTestUtil.generateTestFolders(publicTenant.publicUser.restContext, 1, createdFolder => { + // Ensure permission flags for admin + FoldersTestUtil.assertGetFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.publicFolder.id, + fetchedFolder => { + assert.strictEqual(fetchedFolder.canManage, true); + assert.strictEqual(fetchedFolder.canShare, true); + assert.strictEqual(fetchedFolder.canAddItem, true); + + // Ensure permission flags for manager of a folder FoldersTestUtil.assertGetFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.publicFolder.id, + publicTenant.publicUser.restContext, + createdFolder.id, fetchedFolder => { assert.strictEqual(fetchedFolder.canManage, true); assert.strictEqual(fetchedFolder.canShare, true); assert.strictEqual(fetchedFolder.canAddItem, true); - // Ensure permission flags for manager of a folder + // Ensure permission flags for non-manager user on non-private folder FoldersTestUtil.assertGetFolderSucceeds( publicTenant.publicUser.restContext, - createdFolder.id, + publicTenant.loggedinFolder.id, fetchedFolder => { - assert.strictEqual(fetchedFolder.canManage, true); + assert.strictEqual(fetchedFolder.canManage, false); assert.strictEqual(fetchedFolder.canShare, true); - assert.strictEqual(fetchedFolder.canAddItem, true); + assert.strictEqual(fetchedFolder.canAddItem, false); - // Ensure permission flags for non-manager user on non-private folder - FoldersTestUtil.assertGetFolderSucceeds( - publicTenant.publicUser.restContext, - publicTenant.loggedinFolder.id, - fetchedFolder => { - assert.strictEqual(fetchedFolder.canManage, false); - assert.strictEqual(fetchedFolder.canShare, true); - assert.strictEqual(fetchedFolder.canAddItem, false); - - // Ensure permission flags for non-manager user on private folder - FoldersTestUtil.assertShareFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, + // Ensure permission flags for non-manager user on private folder + FoldersTestUtil.assertShareFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + [publicTenant.publicUser], + () => { + FoldersTestUtil.assertGetFolderSucceeds( + publicTenant.publicUser.restContext, publicTenant.privateFolder.id, - [publicTenant.publicUser], - () => { + fetchedFolder => { + assert.strictEqual(fetchedFolder.canManage, false); + assert.strictEqual(fetchedFolder.canShare, false); + assert.strictEqual(fetchedFolder.canAddItem, false); + + // Ensure permission flags for non-manager user in another public tenant FoldersTestUtil.assertGetFolderSucceeds( - publicTenant.publicUser.restContext, - publicTenant.privateFolder.id, + publicTenant1.publicUser.restContext, + publicTenant.publicFolder.id, fetchedFolder => { assert.strictEqual(fetchedFolder.canManage, false); - assert.strictEqual(fetchedFolder.canShare, false); + assert.strictEqual(fetchedFolder.canShare, true); assert.strictEqual(fetchedFolder.canAddItem, false); - // Ensure permission flags for non-manager user in another public tenant + // Ensure permission flags for non-manager user in another private tenant FoldersTestUtil.assertGetFolderSucceeds( - publicTenant1.publicUser.restContext, + privateTenant.publicUser.restContext, publicTenant.publicFolder.id, fetchedFolder => { assert.strictEqual(fetchedFolder.canManage, false); - assert.strictEqual(fetchedFolder.canShare, true); + assert.strictEqual(fetchedFolder.canShare, false); assert.strictEqual(fetchedFolder.canAddItem, false); - - // Ensure permission flags for non-manager user in another private tenant - FoldersTestUtil.assertGetFolderSucceeds( - privateTenant.publicUser.restContext, - publicTenant.publicFolder.id, - fetchedFolder => { - assert.strictEqual(fetchedFolder.canManage, false); - assert.strictEqual(fetchedFolder.canShare, false); - assert.strictEqual(fetchedFolder.canAddItem, false); - return callback(); - } - ); + return callback(); } ); } @@ -1838,8 +1602,8 @@ describe('Folders', () => { ); } ); - } - ); + }); + }); }); }); @@ -1854,24 +1618,19 @@ describe('Folders', () => { // Ensure deleting using an invalid id results in an error FoldersTestUtil.assertDeleteFolderFails(simong.restContext, 'invalidid', 400, () => { // Ensure deleting using a non-existing id results in a 404 - FoldersTestUtil.assertDeleteFolderFails( - simong.restContext, - 'f:oaetest:nonexistingid', - 404, - () => { - // Sanity-check it was not removed - FoldersTestUtil.assertGetFoldersLibrarySucceeds( - simong.restContext, - simong.user.id, - null, - null, - result => { - assert.strictEqual(result.results.length, 1); - return callback(); - } - ); - } - ); + FoldersTestUtil.assertDeleteFolderFails(simong.restContext, 'f:oaetest:nonexistingid', 404, () => { + // Sanity-check it was not removed + FoldersTestUtil.assertGetFoldersLibrarySucceeds( + simong.restContext, + simong.user.id, + null, + null, + result => { + assert.strictEqual(result.results.length, 1); + return callback(); + } + ); + }); }); }); }); @@ -1900,43 +1659,33 @@ describe('Folders', () => { // Members cannot delete folders FoldersTestUtil.assertDeleteFolderFails(nico.restContext, folder.id, 401, () => { // Tenant admins from other tenants cannot delete the folder - FoldersTestUtil.assertDeleteFolderFails( - gtAdminRestContext, - folder.id, - 401, - () => { - // Sanity-check it was not removed - FoldersTestUtil.assertGetFoldersLibrarySucceeds( - simong.restContext, - simong.user.id, - null, - null, - result => { - assert.strictEqual(result.results.length, 1); + FoldersTestUtil.assertDeleteFolderFails(gtAdminRestContext, folder.id, 401, () => { + // Sanity-check it was not removed + FoldersTestUtil.assertGetFoldersLibrarySucceeds( + simong.restContext, + simong.user.id, + null, + null, + result => { + assert.strictEqual(result.results.length, 1); - // Sanity-check a manager can delete it - FoldersTestUtil.assertDeleteFolderSucceeds( + // Sanity-check a manager can delete it + FoldersTestUtil.assertDeleteFolderSucceeds(simong.restContext, folder.id, false, () => { + // Sanity-check it was not removed + FoldersTestUtil.assertGetFoldersLibrarySucceeds( simong.restContext, - folder.id, - false, - () => { - // Sanity-check it was not removed - FoldersTestUtil.assertGetFoldersLibrarySucceeds( - simong.restContext, - simong.user.id, - null, - null, - result => { - assert.strictEqual(result.results.length, 0); - return callback(); - } - ); + simong.user.id, + null, + null, + result => { + assert.strictEqual(result.results.length, 0); + return callback(); } ); - } - ); - } - ); + }); + } + ); + }); }); }); }); @@ -1982,26 +1731,16 @@ describe('Folders', () => { const contentIds = _.pluck(args, 'id'); // Add the content to the folder - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - simong.restContext, - folder.id, - contentIds, - () => { - // Sanity-check each folder is an authz member of the content item - checkContentMembers(contentIds, [simong.user.id, folder.groupId], () => { - // Delete the folder - FoldersTestUtil.assertDeleteFolderSucceeds( - simong.restContext, - folder.id, - false, - () => { - // The folder should no longer be an authz member of the content items - checkContentMembers(contentIds, [simong.user.id], callback); - } - ); + FoldersTestUtil.assertAddContentItemsToFolderSucceeds(simong.restContext, folder.id, contentIds, () => { + // Sanity-check each folder is an authz member of the content item + checkContentMembers(contentIds, [simong.user.id, folder.groupId], () => { + // Delete the folder + FoldersTestUtil.assertDeleteFolderSucceeds(simong.restContext, folder.id, false, () => { + // The folder should no longer be an authz member of the content items + checkContentMembers(contentIds, [simong.user.id], callback); }); - } - ); + }); + }); }); }); }); @@ -2066,11 +1805,7 @@ describe('Folders', () => { */ it('verify sharing with multiple users gives all access to a folder', callback => { FoldersTestUtil.setupMultiTenantPrivacyEntities(publicTenant => { - const userInfosToShare = [ - publicTenant.publicUser, - publicTenant.loggedinUser, - publicTenant.privateUser - ]; + const userInfosToShare = [publicTenant.publicUser, publicTenant.loggedinUser, publicTenant.privateUser]; // Give access to the private folder to a user FoldersTestUtil.assertShareFolderSucceeds( @@ -2241,337 +1976,329 @@ describe('Folders', () => { * Test that verifies authorization of sharing a folder for a regular (non-member) user of the same tenant */ it('verify sharing authorization for a regular user', callback => { - FoldersTestUtil.setupMultiTenantPrivacyEntities( - (publicTenant, publicTenant1, privateTenant) => { - // Ensure regular user can only share public and loggedin folders - FoldersTestUtil.assertShareFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - [publicTenant.loggedinUser], - () => { - FoldersTestUtil.assertShareFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.loggedinFolder.id, - [publicTenant.loggedinUser], - () => { - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.privateFolder.id, - [publicTenant.loggedinUser.user.id], - 401, - () => { - // Ensure regular user cannot share with user profiles with which they cannot interact - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.loggedinFolder.id, - [publicTenant.privateUser.user.id], - 401, - () => { - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.loggedinFolder.id, - [publicTenant1.publicUser.user.id], - 401, - () => { - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.loggedinFolder.id, - [publicTenant1.loggedinUser.user.id], - 401, - () => { - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.loggedinFolder.id, - [publicTenant1.privateUser.user.id], - 401, - () => { - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.loggedinFolder.id, - [privateTenant.publicUser.user.id], - 401, - () => { - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.loggedinFolder.id, - [privateTenant.loggedinUser.user.id], - 401, - () => { - return FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.loggedinFolder.id, - [privateTenant.privateUser.user.id], - 401, - callback - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1, privateTenant) => { + // Ensure regular user can only share public and loggedin folders + FoldersTestUtil.assertShareFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + [publicTenant.loggedinUser], + () => { + FoldersTestUtil.assertShareFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.loggedinFolder.id, + [publicTenant.loggedinUser], + () => { + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, + [publicTenant.loggedinUser.user.id], + 401, + () => { + // Ensure regular user cannot share with user profiles with which they cannot interact + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.loggedinFolder.id, + [publicTenant.privateUser.user.id], + 401, + () => { + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.loggedinFolder.id, + [publicTenant1.publicUser.user.id], + 401, + () => { + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.loggedinFolder.id, + [publicTenant1.loggedinUser.user.id], + 401, + () => { + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.loggedinFolder.id, + [publicTenant1.privateUser.user.id], + 401, + () => { + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.loggedinFolder.id, + [privateTenant.publicUser.user.id], + 401, + () => { + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.loggedinFolder.id, + [privateTenant.loggedinUser.user.id], + 401, + () => { + return FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.loggedinFolder.id, + [privateTenant.privateUser.user.id], + 401, + callback + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); }); /** * Test that verifies authorization of sharing a folder for a regular (non-member) user of a different tenant */ it('verify sharing authorization for a cross-tenant user', callback => { - FoldersTestUtil.setupMultiTenantPrivacyEntities( - (publicTenant, publicTenant1, privateTenant) => { - // Ensure regular cross-tenant user can only share public folders of another tenant - FoldersTestUtil.assertShareFolderSucceeds( - publicTenant.adminRestContext, - publicTenant1.publicUser.restContext, - publicTenant.publicFolder.id, - [publicTenant.publicUser], - () => { - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant1.publicUser.restContext, - publicTenant.loggedinFolder.id, - [publicTenant.publicUser.user.id], - 401, - () => { - return FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant1.publicUser.restContext, - publicTenant.privateFolder.id, - [publicTenant.publicUser.user.id], - 401, - callback - ); - } - ); - } - ); - } - ); + FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1, privateTenant) => { + // Ensure regular cross-tenant user can only share public folders of another tenant + FoldersTestUtil.assertShareFolderSucceeds( + publicTenant.adminRestContext, + publicTenant1.publicUser.restContext, + publicTenant.publicFolder.id, + [publicTenant.publicUser], + () => { + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant1.publicUser.restContext, + publicTenant.loggedinFolder.id, + [publicTenant.publicUser.user.id], + 401, + () => { + return FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant1.publicUser.restContext, + publicTenant.privateFolder.id, + [publicTenant.publicUser.user.id], + 401, + callback + ); + } + ); + } + ); + }); }); /** * Test that verifies authorization of sharing a folder for a manager user */ it('verify sharing authorization for a manager user', callback => { - FoldersTestUtil.setupMultiTenantPrivacyEntities( - (publicTenant, publicTenant1, privateTenant) => { - // Make the public user a manager of the private folder - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - _memberUpdate(publicTenant.publicUser, 'manager'), - () => { - // Ensure manager user can share all items in own tenant - FoldersTestUtil.assertShareFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - [publicTenant.loggedinUser], - () => { - FoldersTestUtil.assertShareFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.loggedinFolder.id, - [publicTenant.loggedinUser], - () => { - FoldersTestUtil.assertShareFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.privateFolder.id, - [publicTenant.loggedinUser], - () => { - // Ensure manager user cannot share with user profiles to which they cannot interact - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.privateFolder.id, - [publicTenant.privateUser.user.id], - 401, - () => { - FoldersTestUtil.assertShareFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.privateFolder.id, - [publicTenant1.publicUser], - () => { - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.privateFolder.id, - [publicTenant1.loggedinUser.user.id], - 401, - () => { - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.privateFolder.id, - [publicTenant1.privateUser.user.id], - 401, - () => { - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.privateFolder.id, - [privateTenant.publicUser.user.id], - 401, - () => { - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.privateFolder.id, - [privateTenant.loggedinUser.user.id], - 401, - () => { - return FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.privateFolder.id, - [privateTenant.privateUser.user.id], - 401, - callback - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1, privateTenant) => { + // Make the public user a manager of the private folder + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + _memberUpdate(publicTenant.publicUser, 'manager'), + () => { + // Ensure manager user can share all items in own tenant + FoldersTestUtil.assertShareFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + [publicTenant.loggedinUser], + () => { + FoldersTestUtil.assertShareFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.loggedinFolder.id, + [publicTenant.loggedinUser], + () => { + FoldersTestUtil.assertShareFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, + [publicTenant.loggedinUser], + () => { + // Ensure manager user cannot share with user profiles to which they cannot interact + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, + [publicTenant.privateUser.user.id], + 401, + () => { + FoldersTestUtil.assertShareFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, + [publicTenant1.publicUser], + () => { + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, + [publicTenant1.loggedinUser.user.id], + 401, + () => { + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, + [publicTenant1.privateUser.user.id], + 401, + () => { + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, + [privateTenant.publicUser.user.id], + 401, + () => { + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, + [privateTenant.loggedinUser.user.id], + 401, + () => { + return FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, + [privateTenant.privateUser.user.id], + 401, + callback + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); }); /** * Test that verifies authorization of sharing a folder for an administrative user */ it('verify sharing authorization for an admin user', callback => { - FoldersTestUtil.setupMultiTenantPrivacyEntities( - (publicTenant, publicTenant1, privateTenant) => { - // Ensure admin user can share all items in own tenant - FoldersTestUtil.assertShareFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.publicFolder.id, - [publicTenant.loggedinUser], - () => { - FoldersTestUtil.assertShareFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.loggedinFolder.id, - [publicTenant.loggedinUser], - () => { - FoldersTestUtil.assertShareFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - [publicTenant.loggedinUser], - () => { - // Ensure admin user cannot share with user profiles to which they cannot interact - FoldersTestUtil.assertShareFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - [publicTenant.privateUser], - () => { - FoldersTestUtil.assertShareFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - [publicTenant1.publicUser], - () => { - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - [publicTenant1.loggedinUser.user.id], - 401, - () => { - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - [publicTenant1.privateUser.user.id], - 401, - () => { - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - [privateTenant.publicUser.user.id], - 401, - () => { - FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - [privateTenant.loggedinUser.user.id], - 401, - () => { - return FoldersTestUtil.assertShareFolderFails( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - [privateTenant.privateUser.user.id], - 401, - callback - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1, privateTenant) => { + // Ensure admin user can share all items in own tenant + FoldersTestUtil.assertShareFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.publicFolder.id, + [publicTenant.loggedinUser], + () => { + FoldersTestUtil.assertShareFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.loggedinFolder.id, + [publicTenant.loggedinUser], + () => { + FoldersTestUtil.assertShareFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + [publicTenant.loggedinUser], + () => { + // Ensure admin user cannot share with user profiles to which they cannot interact + FoldersTestUtil.assertShareFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + [publicTenant.privateUser], + () => { + FoldersTestUtil.assertShareFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + [publicTenant1.publicUser], + () => { + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + [publicTenant1.loggedinUser.user.id], + 401, + () => { + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + [publicTenant1.privateUser.user.id], + 401, + () => { + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + [privateTenant.publicUser.user.id], + 401, + () => { + FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + [privateTenant.loggedinUser.user.id], + 401, + () => { + return FoldersTestUtil.assertShareFolderFails( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + [privateTenant.privateUser.user.id], + 401, + callback + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); }); }); @@ -2640,48 +2367,35 @@ describe('Folders', () => { memberUpdates, () => { // Ensure the folder id must be a valid resource id - FoldersTestUtil.assertGetFolderMembersFails( - mrvisser.restContext, - 'notavalidid', - null, - null, - 400, - () => { - // Ensure the folder must exist - FoldersTestUtil.assertGetFolderMembersFails( - mrvisser.restContext, - 'x:oaetest:nonexistingid', - null, - null, - 404, - () => { - // Ensure limit has a minimum of 1 + FoldersTestUtil.assertGetFolderMembersFails(mrvisser.restContext, 'notavalidid', null, null, 400, () => { + // Ensure the folder must exist + FoldersTestUtil.assertGetFolderMembersFails( + mrvisser.restContext, + 'x:oaetest:nonexistingid', + null, + null, + 404, + () => { + // Ensure limit has a minimum of 1 + FoldersTestUtil.assertGetFolderMembersSucceeds(mrvisser.restContext, folder.id, null, 0, result => { + assert.strictEqual(result.results.length, 1); + + // Ensure limit defaults to 10 FoldersTestUtil.assertGetFolderMembersSucceeds( mrvisser.restContext, folder.id, null, - 0, + null, result => { - assert.strictEqual(result.results.length, 1); - - // Ensure limit defaults to 10 - FoldersTestUtil.assertGetFolderMembersSucceeds( - mrvisser.restContext, - folder.id, - null, - null, - result => { - assert.strictEqual(result.results.length, 10); + assert.strictEqual(result.results.length, 10); - return callback(); - } - ); + return callback(); } ); - } - ); - } - ); + }); + } + ); + }); } ); }); @@ -2804,10 +2518,8 @@ describe('Folders', () => { () => { // Ensure same-tenant user with access can now view the private folder members FoldersTestUtil.assertGetFolderMembersSucceeds( - publicTenant.publicUser - .restContext, - publicTenant.privateFolder - .id, + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, null, null, () => { @@ -2815,40 +2527,27 @@ describe('Folders', () => { FoldersTestUtil.assertShareFolderSucceeds( publicTenant.adminRestContext, publicTenant.adminRestContext, - publicTenant - .loggedinFolder.id, - [ - publicTenant1.publicUser - ], + publicTenant.loggedinFolder.id, + [publicTenant1.publicUser], () => { FoldersTestUtil.assertShareFolderSucceeds( publicTenant.adminRestContext, publicTenant.adminRestContext, - publicTenant - .privateFolder - .id, - [ - publicTenant1.publicUser - ], + publicTenant.privateFolder.id, + [publicTenant1.publicUser], () => { // Ensure the cross-tenant user can now see the loggedin and private folders with explicit access FoldersTestUtil.assertGetFolderMembersSucceeds( - publicTenant1 - .publicUser + publicTenant1.publicUser .restContext, - publicTenant - .loggedinFolder - .id, + publicTenant.loggedinFolder.id, null, null, () => { FoldersTestUtil.assertGetFolderMembersSucceeds( - publicTenant1 - .publicUser + publicTenant1.publicUser .restContext, - publicTenant - .privateFolder - .id, + publicTenant.privateFolder.id, null, null, () => { @@ -3038,10 +2737,7 @@ describe('Folders', () => { .findWhere({ visibility: 'private' }) .value(); - assert.strictEqual( - privateMember.displayName, - publicTenant.privateUser.user.publicAlias - ); + assert.strictEqual(privateMember.displayName, publicTenant.privateUser.user.publicAlias); assert.ok(publicTenant.privateUser.user.profilePath); assert.ok(!privateMember.profilePath); return callback(); @@ -3095,30 +2791,26 @@ describe('Folders', () => { _memberUpdate(bert, 'manager'), () => { // Ensure Bert can view the folder once again - FoldersTestUtil.assertGetFolderSucceeds( - bert.restContext, - folder.id, - () => { - // Ensure Bert can now demote mrvisser to viewer, O noez! - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - bert.restContext, - bert.restContext, - folder.id, - _memberUpdate(mrvisser), - () => { - // Ensure mrvisser can no longer update the permissions - return FoldersTestUtil.assertUpdateFolderMembersFails( - bert.restContext, - mrvisser.restContext, - folder.id, - _memberUpdate(bert.user.id, 'manager'), - 401, - callback - ); - } - ); - } - ); + FoldersTestUtil.assertGetFolderSucceeds(bert.restContext, folder.id, () => { + // Ensure Bert can now demote mrvisser to viewer, O noez! + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + bert.restContext, + bert.restContext, + folder.id, + _memberUpdate(mrvisser), + () => { + // Ensure mrvisser can no longer update the permissions + return FoldersTestUtil.assertUpdateFolderMembersFails( + bert.restContext, + mrvisser.restContext, + folder.id, + _memberUpdate(bert.user.id, 'manager'), + 401, + callback + ); + } + ); + }); } ); }); @@ -3335,69 +3027,175 @@ describe('Folders', () => { }); /** - * Test that verifies authorization of setting folder members as an administrative user + * Test that verifies authorization of setting folder members as an administrative user + */ + it('verify set folder members authorization for an admin user', callback => { + // Setup folders and users for different visibilities and tenants + FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1, privateTenant) => { + // Create an extra folder that is not managed by an admin user + FoldersTestUtil.generateTestFolders(publicTenant.publicUser.restContext, 1, folder => { + // Ensure admin can set members a folder they don't explicitly manage + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + publicTenant.publicUser.restContext, + publicTenant.adminRestContext, + folder.id, + _memberUpdate(publicTenant.loggedinUser, 'manager'), + () => { + // Ensure admin cannot set members for user profiles with which they cannot interact + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.loggedinFolder.id, + _memberUpdate(publicTenant.privateUser), + () => { + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.loggedinFolder.id, + _memberUpdate(publicTenant1.publicUser), + () => { + FoldersTestUtil.assertUpdateFolderMembersFails( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.loggedinFolder.id, + _memberUpdate(publicTenant1.loggedinUser.user.id), + 401, + () => { + FoldersTestUtil.assertUpdateFolderMembersFails( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.loggedinFolder.id, + _memberUpdate(publicTenant1.privateUser.user.id), + 401, + () => { + FoldersTestUtil.assertUpdateFolderMembersFails( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.loggedinFolder.id, + _memberUpdate(privateTenant.publicUser.user.id), + 401, + () => { + FoldersTestUtil.assertUpdateFolderMembersFails( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.loggedinFolder.id, + _memberUpdate(privateTenant.loggedinUser.user.id), + 401, + () => { + return FoldersTestUtil.assertUpdateFolderMembersFails( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.loggedinFolder.id, + _memberUpdate(privateTenant.privateUser.user.id), + 401, + callback + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); + }); + }); + + /** + * Test that verifies authorization of setting members on a folder as a regular user */ - it('verify set folder members authorization for an admin user', callback => { + it('verify set folder members authorization for a regular user', callback => { // Setup folders and users for different visibilities and tenants - FoldersTestUtil.setupMultiTenantPrivacyEntities( - (publicTenant, publicTenant1, privateTenant) => { - // Create an extra folder that is not managed by an admin user - FoldersTestUtil.generateTestFolders(publicTenant.publicUser.restContext, 1, folder => { - // Ensure admin can set members a folder they don't explicitly manage + FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1, privateTenant) => { + // Ensure the user cannot set members a folder they don't explicitly manage + FoldersTestUtil.assertUpdateFolderMembersFails( + publicTenant.adminRestContext, + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + _memberUpdate(publicTenant.loggedinUser.user.id), + 401, + () => { + // Make the user a viewer and ensure they still can't set permissions FoldersTestUtil.assertUpdateFolderMembersSucceeds( - publicTenant.publicUser.restContext, publicTenant.adminRestContext, - folder.id, - _memberUpdate(publicTenant.loggedinUser, 'manager'), + publicTenant.adminRestContext, + publicTenant.publicFolder.id, + _memberUpdate(publicTenant.publicUser), () => { - // Ensure admin cannot set members for user profiles with which they cannot interact - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - publicTenant.adminRestContext, + FoldersTestUtil.assertUpdateFolderMembersFails( publicTenant.adminRestContext, - publicTenant.loggedinFolder.id, - _memberUpdate(publicTenant.privateUser), + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + _memberUpdate(publicTenant.loggedinUser.user.id), + 401, () => { + // Make the user a manager so they can update permissions FoldersTestUtil.assertUpdateFolderMembersSucceeds( publicTenant.adminRestContext, publicTenant.adminRestContext, - publicTenant.loggedinFolder.id, - _memberUpdate(publicTenant1.publicUser), + publicTenant.publicFolder.id, + _memberUpdate(publicTenant.publicUser, 'manager'), () => { + // Ensure the manager user cannot set members for user profiles with which they cannot interact FoldersTestUtil.assertUpdateFolderMembersFails( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.loggedinFolder.id, - _memberUpdate(publicTenant1.loggedinUser.user.id), + publicTenant.publicUser.restContext, + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + _memberUpdate(publicTenant.privateUser.user.id), 401, () => { - FoldersTestUtil.assertUpdateFolderMembersFails( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.loggedinFolder.id, - _memberUpdate(publicTenant1.privateUser.user.id), - 401, + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + publicTenant.publicUser.restContext, + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + _memberUpdate(publicTenant1.publicUser), () => { FoldersTestUtil.assertUpdateFolderMembersFails( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.loggedinFolder.id, - _memberUpdate(privateTenant.publicUser.user.id), + publicTenant.publicUser.restContext, + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + _memberUpdate(publicTenant1.loggedinUser.user.id), 401, () => { FoldersTestUtil.assertUpdateFolderMembersFails( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.loggedinFolder.id, - _memberUpdate(privateTenant.loggedinUser.user.id), + publicTenant.publicUser.restContext, + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + _memberUpdate(publicTenant1.privateUser.user.id), 401, () => { - return FoldersTestUtil.assertUpdateFolderMembersFails( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.loggedinFolder.id, - _memberUpdate(privateTenant.privateUser.user.id), + FoldersTestUtil.assertUpdateFolderMembersFails( + publicTenant.publicUser.restContext, + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + _memberUpdate(privateTenant.publicUser.user.id), 401, - callback + () => { + FoldersTestUtil.assertUpdateFolderMembersFails( + publicTenant.publicUser.restContext, + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + _memberUpdate(privateTenant.loggedinUser.user.id), + 401, + () => { + return FoldersTestUtil.assertUpdateFolderMembersFails( + publicTenant.publicUser.restContext, + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + _memberUpdate(privateTenant.privateUser.user.id), + 401, + callback + ); + } + ); + } ); } ); @@ -3413,121 +3211,9 @@ describe('Folders', () => { ); } ); - }); - } - ); - }); - - /** - * Test that verifies authorization of setting members on a folder as a regular user - */ - it('verify set folder members authorization for a regular user', callback => { - // Setup folders and users for different visibilities and tenants - FoldersTestUtil.setupMultiTenantPrivacyEntities( - (publicTenant, publicTenant1, privateTenant) => { - // Ensure the user cannot set members a folder they don't explicitly manage - FoldersTestUtil.assertUpdateFolderMembersFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - _memberUpdate(publicTenant.loggedinUser.user.id), - 401, - () => { - // Make the user a viewer and ensure they still can't set permissions - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.publicFolder.id, - _memberUpdate(publicTenant.publicUser), - () => { - FoldersTestUtil.assertUpdateFolderMembersFails( - publicTenant.adminRestContext, - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - _memberUpdate(publicTenant.loggedinUser.user.id), - 401, - () => { - // Make the user a manager so they can update permissions - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.publicFolder.id, - _memberUpdate(publicTenant.publicUser, 'manager'), - () => { - // Ensure the manager user cannot set members for user profiles with which they cannot interact - FoldersTestUtil.assertUpdateFolderMembersFails( - publicTenant.publicUser.restContext, - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - _memberUpdate(publicTenant.privateUser.user.id), - 401, - () => { - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - publicTenant.publicUser.restContext, - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - _memberUpdate(publicTenant1.publicUser), - () => { - FoldersTestUtil.assertUpdateFolderMembersFails( - publicTenant.publicUser.restContext, - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - _memberUpdate(publicTenant1.loggedinUser.user.id), - 401, - () => { - FoldersTestUtil.assertUpdateFolderMembersFails( - publicTenant.publicUser.restContext, - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - _memberUpdate(publicTenant1.privateUser.user.id), - 401, - () => { - FoldersTestUtil.assertUpdateFolderMembersFails( - publicTenant.publicUser.restContext, - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - _memberUpdate(privateTenant.publicUser.user.id), - 401, - () => { - FoldersTestUtil.assertUpdateFolderMembersFails( - publicTenant.publicUser.restContext, - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - _memberUpdate(privateTenant.loggedinUser.user.id), - 401, - () => { - return FoldersTestUtil.assertUpdateFolderMembersFails( - publicTenant.publicUser.restContext, - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - _memberUpdate( - privateTenant.privateUser.user.id - ), - 401, - callback - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + } + ); + }); }); }); @@ -3553,24 +3239,12 @@ describe('Folders', () => { 3, 'private', (privateFolder1, privateFolder2, privateFolder3) => { - const publicFolderIds = _.pluck( - [publicFolder1, publicFolder2, publicFolder3], - 'id' - ); - const loggedinFolderIds = _.pluck( - [loggedinFolder1, loggedinFolder2, loggedinFolder3], - 'id' - ); - const privateFolderIds = _.pluck( - [privateFolder1, privateFolder2, privateFolder3], - 'id' - ); + const publicFolderIds = _.pluck([publicFolder1, publicFolder2, publicFolder3], 'id'); + const loggedinFolderIds = _.pluck([loggedinFolder1, loggedinFolder2, loggedinFolder3], 'id'); + const privateFolderIds = _.pluck([privateFolder1, privateFolder2, privateFolder3], 'id'); const expectedPublicFoldersLibraryIds = publicFolderIds.slice(); - const expectedLoggedinFoldersLibraryIds = _.union( - publicFolderIds, - loggedinFolderIds - ); + const expectedLoggedinFoldersLibraryIds = _.union(publicFolderIds, loggedinFolderIds); const expectedPrivateFoldersLibraryIds = _.chain(publicFolderIds) .union(loggedinFolderIds) .union(privateFolderIds) @@ -3828,82 +3502,75 @@ describe('Folders', () => { assert.ok(!err); FoldersTestUtil.generateTestFolders(mrvisser.restContext, 30, () => { // Ensure we must provide a valid and existing principal id - FoldersTestUtil.assertGetFoldersLibraryFails( - mrvisser.restContext, - 'notavalidid', - null, - 15, - 400, - () => { - FoldersTestUtil.assertGetFoldersLibraryFails( - mrvisser.restContext, - 'c:oaetest:notaprincipalid', - null, - 15, - 400, - () => { - FoldersTestUtil.assertGetFoldersLibraryFails( - mrvisser.restContext, - 'g:oaetest:nonexistingid', - null, - 15, - 404, - () => { - FoldersTestUtil.assertGetFoldersLibraryFails( - mrvisser.restContext, - 'u:oaetest:nonexistingid', - null, - 15, - 404, - () => { - // Ensure limit is greater than or equal to 1, less than or equal to 25, and defaults to 10 - FoldersTestUtil.assertGetFoldersLibrarySucceeds( - mrvisser.restContext, - mrvisser.user.id, - null, - 0, - result => { - assert.strictEqual(result.results.length, 1); - FoldersTestUtil.assertGetFoldersLibrarySucceeds( - mrvisser.restContext, - mrvisser.user.id, - null, - null, - result => { - assert.strictEqual(result.results.length, 12); - FoldersTestUtil.assertGetFoldersLibrarySucceeds( - mrvisser.restContext, - mrvisser.user.id, - null, - 100, - result => { - assert.strictEqual(result.results.length, 25); + FoldersTestUtil.assertGetFoldersLibraryFails(mrvisser.restContext, 'notavalidid', null, 15, 400, () => { + FoldersTestUtil.assertGetFoldersLibraryFails( + mrvisser.restContext, + 'c:oaetest:notaprincipalid', + null, + 15, + 400, + () => { + FoldersTestUtil.assertGetFoldersLibraryFails( + mrvisser.restContext, + 'g:oaetest:nonexistingid', + null, + 15, + 404, + () => { + FoldersTestUtil.assertGetFoldersLibraryFails( + mrvisser.restContext, + 'u:oaetest:nonexistingid', + null, + 15, + 404, + () => { + // Ensure limit is greater than or equal to 1, less than or equal to 25, and defaults to 10 + FoldersTestUtil.assertGetFoldersLibrarySucceeds( + mrvisser.restContext, + mrvisser.user.id, + null, + 0, + result => { + assert.strictEqual(result.results.length, 1); + FoldersTestUtil.assertGetFoldersLibrarySucceeds( + mrvisser.restContext, + mrvisser.user.id, + null, + null, + result => { + assert.strictEqual(result.results.length, 12); + FoldersTestUtil.assertGetFoldersLibrarySucceeds( + mrvisser.restContext, + mrvisser.user.id, + null, + 100, + result => { + assert.strictEqual(result.results.length, 25); - // Ensure the base input provides the expected results - FoldersTestUtil.assertGetFoldersLibrarySucceeds( - mrvisser.restContext, - mrvisser.user.id, - null, - 15, - result => { - assert.strictEqual(result.results.length, 15); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + // Ensure the base input provides the expected results + FoldersTestUtil.assertGetFoldersLibrarySucceeds( + mrvisser.restContext, + mrvisser.user.id, + null, + 15, + result => { + assert.strictEqual(result.results.length, 15); + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); }); }); }); @@ -4000,65 +3667,44 @@ describe('Folders', () => { [], folder => { // Page items by 1 and ensure we get them all with the correct number of requests - FoldersTestUtil.getAllFoldersInLibrary( - simong.restContext, - simong.user.id, - { batchSize: 1 }, - folders => { + FoldersTestUtil.getAllFoldersInLibrary(simong.restContext, simong.user.id, { batchSize: 1 }, folders => { + assert.strictEqual(folders.length, 1); + assert.strictEqual(folders[0].id, folder.id); + FoldersTestUtil.getAllFoldersInLibrary(nico.restContext, nico.user.id, { batchSize: 1 }, folders => { assert.strictEqual(folders.length, 1); assert.strictEqual(folders[0].id, folder.id); - FoldersTestUtil.getAllFoldersInLibrary( - nico.restContext, - nico.user.id, - { batchSize: 1 }, - folders => { - assert.strictEqual(folders.length, 1); - assert.strictEqual(folders[0].id, folder.id); - - // Trigger a manual update - FoldersFolderLibrary.update( - [simong.user.id, nico.user.id], - folder, - null, - (err, newFolder) => { - assert.ok(!err); - assert.notStrictEqual(folder.lastModified, newFolder.lastModified); - // Assert the folders are still in the libraries - FoldersTestUtil.getAllFoldersInLibrary( - simong.restContext, - simong.user.id, - { batchSize: 1 }, - folders => { - assert.strictEqual(folders.length, 1); - assert.strictEqual(folders[0].id, folder.id); - assert.strictEqual( - folders[0].lastModified, - newFolder.lastModified.toString() - ); - FoldersTestUtil.getAllFoldersInLibrary( - nico.restContext, - nico.user.id, - { batchSize: 1 }, - folders => { - assert.strictEqual(folders.length, 1); - assert.strictEqual(folders[0].id, folder.id); - assert.strictEqual( - folders[0].lastModified, - newFolder.lastModified.toString() - ); + // Trigger a manual update + FoldersFolderLibrary.update([simong.user.id, nico.user.id], folder, null, (err, newFolder) => { + assert.ok(!err); + assert.notStrictEqual(folder.lastModified, newFolder.lastModified); + + // Assert the folders are still in the libraries + FoldersTestUtil.getAllFoldersInLibrary( + simong.restContext, + simong.user.id, + { batchSize: 1 }, + folders => { + assert.strictEqual(folders.length, 1); + assert.strictEqual(folders[0].id, folder.id); + assert.strictEqual(folders[0].lastModified, newFolder.lastModified.toString()); + FoldersTestUtil.getAllFoldersInLibrary( + nico.restContext, + nico.user.id, + { batchSize: 1 }, + folders => { + assert.strictEqual(folders.length, 1); + assert.strictEqual(folders[0].id, folder.id); + assert.strictEqual(folders[0].lastModified, newFolder.lastModified.toString()); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + return callback(); + } + ); + } + ); + }); + }); + }); } ); }); @@ -4103,23 +3749,15 @@ describe('Folders', () => { assert.ok(_.findWhere(folders.results, { id: nicosFolder.id })); // Deleting the folder will cause it to remove from the managed folders list - FoldersTestUtil.assertDeleteFolderSucceeds( - simong.restContext, - simonsFolder.id, - false, - () => { - // Only Nico's folder should remain - RestAPI.Folders.getManagedFolders( - simong.restContext, - (err, folders) => { - assert.ok(!err); - assert.strictEqual(folders.results.length, 1); - assert.strictEqual(folders.results[0].id, nicosFolder.id); - return callback(); - } - ); - } - ); + FoldersTestUtil.assertDeleteFolderSucceeds(simong.restContext, simonsFolder.id, false, () => { + // Only Nico's folder should remain + RestAPI.Folders.getManagedFolders(simong.restContext, (err, folders) => { + assert.ok(!err); + assert.strictEqual(folders.results.length, 1); + assert.strictEqual(folders.results[0].id, nicosFolder.id); + return callback(); + }); + }); }); } ); @@ -4319,36 +3957,27 @@ describe('Folders', () => { [], (err, link) => { assert.ok(!err); - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - nico.restContext, - folder.id, - [link.id], - () => { - // Simon should now be able to access the private item - RestAPI.Content.getContent(simong.restContext, link.id, (err, content) => { - assert.ok(!err); - assert.strictEqual(content.id, link.id); + FoldersTestUtil.assertAddContentItemsToFolderSucceeds(nico.restContext, folder.id, [link.id], () => { + // Simon should now be able to access the private item + RestAPI.Content.getContent(simong.restContext, link.id, (err, content) => { + assert.ok(!err); + assert.strictEqual(content.id, link.id); - // Simon removes the folder from his library - FoldersTestUtil.assertRemoveFolderFromLibrarySucceeds( - simong.restContext, - simong.user.id, - folder.id, - () => { - // Simon should no longer be able to access the private content item - RestAPI.Content.getContent( - simong.restContext, - link.id, - (err, content) => { - assert.strictEqual(err.code, 401); - return callback(); - } - ); - } - ); - }); - } - ); + // Simon removes the folder from his library + FoldersTestUtil.assertRemoveFolderFromLibrarySucceeds( + simong.restContext, + simong.user.id, + folder.id, + () => { + // Simon should no longer be able to access the private content item + RestAPI.Content.getContent(simong.restContext, link.id, (err, content) => { + assert.strictEqual(err.code, 401); + return callback(); + }); + } + ); + }); + }); } ); } @@ -4502,271 +4131,99 @@ describe('Folders', () => { /** * Test that verifies both managers and administrators can add content items to a folder - */ - it('verify only administrators and managers of folders can add content items to them', callback => { - FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1) => { - RestAPI.User.getMe(publicTenant.adminRestContext, (err, publicTenantAdminMe) => { - assert.ok(!err); - - // Ensure anonymous, regular user, admin from another tenant all cannot add a content item to the folder - FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant.anonymousRestContext, - publicTenant.publicFolder.id, - [publicTenant.publicContent.id], - 401, - () => { - FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - [publicTenant.publicContent.id], - 401, - () => { - FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant1.adminRestContext, - publicTenant.publicFolder.id, - [publicTenant.publicContent.id], - 401, - () => { - // Ensure the folder still has no items - FoldersTestUtil.getAllFolderContentItems( - publicTenant.adminRestContext, - publicTenant.publicFolder.id, - null, - contentItems => { - assert.ok(_.isEmpty(contentItems)); - - // Add public user as a manager of the folder and remove admin as a manager - const memberUpdates = _memberUpdate(publicTenant.publicUser, 'manager'); - memberUpdates[publicTenantAdminMe.id] = false; - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.publicFolder.id, - memberUpdates, - () => { - // Ensure public user and admin can both add an item to the folder - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - [publicTenant.publicContent.id], - () => { - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.publicFolder.id, - [publicTenant.loggedinContent.id], - () => { - // Ensure the folder now has 2 items - FoldersTestUtil.getAllFolderContentItems( - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - null, - contentItems => { - assert.strictEqual(contentItems.length, 2); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - }); - }); - }); - - /** - * Test that verifies authorization for an administrator adding a content item to a folder - */ - it('verify add items to folder authorization for an administrator', callback => { - FoldersTestUtil.setupMultiTenantPrivacyEntities( - (publicTenant, publicTenant1, privateTenant) => { - RestAPI.User.getMe(publicTenant.adminRestContext, (err, publicTenantAdminMe) => { - assert.ok(!err); - - // Admin removes themself from managing each folder while adding a user. This is - // to ensure the test is accurate by admin having no explicit manage access - const memberUpdates = _memberUpdate(publicTenant.publicUser, 'manager'); - memberUpdates[publicTenantAdminMe.id] = false; - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.publicFolder.id, - memberUpdates, - () => { - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.loggedinFolder.id, - memberUpdates, - () => { - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - memberUpdates, - () => { - // Ensure admin can add the public, loggedin and private content items of their tenant to all the folders in their tenant - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.publicFolder.id, - [publicTenant.publicContent.id], - () => { - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.publicFolder.id, - [publicTenant.loggedinContent.id], - () => { - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.publicFolder.id, - [publicTenant.privateContent.id], - () => { - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.loggedinFolder.id, - [publicTenant.publicContent.id], - () => { - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.loggedinFolder.id, - [publicTenant.loggedinContent.id], - () => { - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.loggedinFolder.id, - [publicTenant.privateContent.id], - () => { - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - [publicTenant.publicContent.id], - () => { - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - [publicTenant.loggedinContent.id], - () => { - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - [publicTenant.privateContent.id], - () => { - // Ensure admin can add only the public content item from another public tenant to a folder - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.publicFolder.id, - [publicTenant1.publicContent.id], - () => { - FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant.adminRestContext, - publicTenant.publicFolder.id, - [ - publicTenant1.loggedinContent.id - ], - 401, - () => { - FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant.adminRestContext, - publicTenant.publicFolder.id, - [ - publicTenant1.privateContent - .id - ], - 401, - () => { - // Ensure admin cannot add any content item from another private tenant to a folder - FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant.adminRestContext, - publicTenant.publicFolder - .id, - [ - privateTenant - .publicContent.id - ], - 401, - () => { - FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant.adminRestContext, - publicTenant - .publicFolder.id, - [ - privateTenant - .loggedinContent - .id - ], - 401, - () => { - FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant.adminRestContext, - publicTenant - .publicFolder - .id, - [ - privateTenant - .privateContent - .id - ], - 401, - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - }); - } - ); + */ + it('verify only administrators and managers of folders can add content items to them', callback => { + FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1) => { + RestAPI.User.getMe(publicTenant.adminRestContext, (err, publicTenantAdminMe) => { + assert.ok(!err); + + // Ensure anonymous, regular user, admin from another tenant all cannot add a content item to the folder + FoldersTestUtil.assertAddContentItemsToFolderFails( + publicTenant.anonymousRestContext, + publicTenant.publicFolder.id, + [publicTenant.publicContent.id], + 401, + () => { + FoldersTestUtil.assertAddContentItemsToFolderFails( + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + [publicTenant.publicContent.id], + 401, + () => { + FoldersTestUtil.assertAddContentItemsToFolderFails( + publicTenant1.adminRestContext, + publicTenant.publicFolder.id, + [publicTenant.publicContent.id], + 401, + () => { + // Ensure the folder still has no items + FoldersTestUtil.getAllFolderContentItems( + publicTenant.adminRestContext, + publicTenant.publicFolder.id, + null, + contentItems => { + assert.ok(_.isEmpty(contentItems)); + + // Add public user as a manager of the folder and remove admin as a manager + const memberUpdates = _memberUpdate(publicTenant.publicUser, 'manager'); + memberUpdates[publicTenantAdminMe.id] = false; + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.publicFolder.id, + memberUpdates, + () => { + // Ensure public user and admin can both add an item to the folder + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + [publicTenant.publicContent.id], + () => { + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.publicFolder.id, + [publicTenant.loggedinContent.id], + () => { + // Ensure the folder now has 2 items + FoldersTestUtil.getAllFolderContentItems( + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + null, + contentItems => { + assert.strictEqual(contentItems.length, 2); + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); + }); }); /** - * Test that verifies authorization for an authenticated user adding a content item to a folder + * Test that verifies authorization for an administrator adding a content item to a folder */ - it('verify add items to folder authorization for an authenticated user', callback => { - FoldersTestUtil.setupMultiTenantPrivacyEntities( - (publicTenant, publicTenant1, privateTenant) => { - // Make the public user a manager of each folder so he can add items to them + it('verify add items to folder authorization for an administrator', callback => { + FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1, privateTenant) => { + RestAPI.User.getMe(publicTenant.adminRestContext, (err, publicTenantAdminMe) => { + assert.ok(!err); + + // Admin removes themself from managing each folder while adding a user. This is + // to ensure the test is accurate by admin having no explicit manage access const memberUpdates = _memberUpdate(publicTenant.publicUser, 'manager'); + memberUpdates[publicTenantAdminMe.id] = false; FoldersTestUtil.assertUpdateFolderMembersSucceeds( publicTenant.adminRestContext, publicTenant.adminRestContext, @@ -4785,178 +4242,90 @@ describe('Folders', () => { publicTenant.privateFolder.id, memberUpdates, () => { - // Ensure a user can add the public and loggedin content items of their tenant to the folder + // Ensure admin can add the public, loggedin and private content items of their tenant to all the folders in their tenant FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.publicUser.restContext, + publicTenant.adminRestContext, publicTenant.publicFolder.id, [publicTenant.publicContent.id], () => { FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.publicUser.restContext, + publicTenant.adminRestContext, publicTenant.publicFolder.id, [publicTenant.loggedinContent.id], () => { - FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant.publicUser.restContext, + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + publicTenant.adminRestContext, publicTenant.publicFolder.id, [publicTenant.privateContent.id], - 401, () => { FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.publicUser.restContext, + publicTenant.adminRestContext, publicTenant.loggedinFolder.id, [publicTenant.publicContent.id], () => { FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.publicUser.restContext, + publicTenant.adminRestContext, publicTenant.loggedinFolder.id, [publicTenant.loggedinContent.id], () => { - FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant.publicUser.restContext, + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + publicTenant.adminRestContext, publicTenant.loggedinFolder.id, [publicTenant.privateContent.id], - 401, () => { FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.publicUser.restContext, + publicTenant.adminRestContext, publicTenant.privateFolder.id, [publicTenant.publicContent.id], () => { FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.publicUser.restContext, + publicTenant.adminRestContext, publicTenant.privateFolder.id, [publicTenant.loggedinContent.id], () => { - FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant.publicUser.restContext, + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + publicTenant.adminRestContext, publicTenant.privateFolder.id, [publicTenant.privateContent.id], - 401, () => { - // Once a user has viewer rights, he should be able to add content to any folder he can manage - const contentMemberUpdate = _memberUpdate( - publicTenant.publicUser.user.id - ); - RestAPI.Content.updateMembers( + // Ensure admin can add only the public content item from another public tenant to a folder + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( publicTenant.adminRestContext, - publicTenant.privateContent.id, - contentMemberUpdate, - err => { - assert.ok(!err); - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + [publicTenant1.publicContent.id], + () => { + FoldersTestUtil.assertAddContentItemsToFolderFails( + publicTenant.adminRestContext, publicTenant.publicFolder.id, - [publicTenant.privateContent.id], + [publicTenant1.loggedinContent.id], + 401, () => { - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.publicUser - .restContext, - publicTenant.loggedinFolder.id, - [ - publicTenant.privateContent.id - ], + FoldersTestUtil.assertAddContentItemsToFolderFails( + publicTenant.adminRestContext, + publicTenant.publicFolder.id, + [publicTenant1.privateContent.id], + 401, () => { - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.publicUser - .restContext, - publicTenant.privateFolder - .id, - [ - publicTenant - .privateContent.id - ], + // Ensure admin cannot add any content item from another private tenant to a folder + FoldersTestUtil.assertAddContentItemsToFolderFails( + publicTenant.adminRestContext, + publicTenant.publicFolder.id, + [privateTenant.publicContent.id], + 401, () => { - // Ensure a user can add only the public content item from another public tenant to a folder - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - publicTenant.publicUser - .restContext, - publicTenant - .publicFolder.id, - [ - publicTenant1 - .publicContent.id - ], + FoldersTestUtil.assertAddContentItemsToFolderFails( + publicTenant.adminRestContext, + publicTenant.publicFolder.id, + [privateTenant.loggedinContent.id], + 401, () => { FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant - .publicUser - .restContext, - publicTenant - .publicFolder.id, - [ - publicTenant1 - .loggedinContent - .id - ], + publicTenant.adminRestContext, + publicTenant.publicFolder.id, + [privateTenant.privateContent.id], 401, () => { - FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant - .publicUser - .restContext, - publicTenant - .publicFolder - .id, - [ - publicTenant1 - .privateContent - .id - ], - 401, - () => { - // Ensure a user cannot add any content item from another private tenant to a folder - FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant - .publicUser - .restContext, - publicTenant - .publicFolder - .id, - [ - privateTenant - .publicContent - .id - ], - 401, - () => { - FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant - .publicUser - .restContext, - publicTenant - .publicFolder - .id, - [ - privateTenant - .loggedinContent - .id - ], - 401, - () => { - FoldersTestUtil.assertAddContentItemsToFolderFails( - publicTenant - .publicUser - .restContext, - publicTenant - .publicFolder - .id, - [ - privateTenant - .privateContent - .id - ], - 401, - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); + return callback(); } ); } @@ -4993,8 +4362,202 @@ describe('Folders', () => { ); } ); - } - ); + }); + }); + }); + + /** + * Test that verifies authorization for an authenticated user adding a content item to a folder + */ + it('verify add items to folder authorization for an authenticated user', callback => { + FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1, privateTenant) => { + // Make the public user a manager of each folder so he can add items to them + const memberUpdates = _memberUpdate(publicTenant.publicUser, 'manager'); + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.publicFolder.id, + memberUpdates, + () => { + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.loggedinFolder.id, + memberUpdates, + () => { + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + memberUpdates, + () => { + // Ensure a user can add the public and loggedin content items of their tenant to the folder + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + [publicTenant.publicContent.id], + () => { + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + [publicTenant.loggedinContent.id], + () => { + FoldersTestUtil.assertAddContentItemsToFolderFails( + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + [publicTenant.privateContent.id], + 401, + () => { + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + publicTenant.publicUser.restContext, + publicTenant.loggedinFolder.id, + [publicTenant.publicContent.id], + () => { + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + publicTenant.publicUser.restContext, + publicTenant.loggedinFolder.id, + [publicTenant.loggedinContent.id], + () => { + FoldersTestUtil.assertAddContentItemsToFolderFails( + publicTenant.publicUser.restContext, + publicTenant.loggedinFolder.id, + [publicTenant.privateContent.id], + 401, + () => { + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, + [publicTenant.publicContent.id], + () => { + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, + [publicTenant.loggedinContent.id], + () => { + FoldersTestUtil.assertAddContentItemsToFolderFails( + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, + [publicTenant.privateContent.id], + 401, + () => { + // Once a user has viewer rights, he should be able to add content to any folder he can manage + const contentMemberUpdate = _memberUpdate( + publicTenant.publicUser.user.id + ); + RestAPI.Content.updateMembers( + publicTenant.adminRestContext, + publicTenant.privateContent.id, + contentMemberUpdate, + err => { + assert.ok(!err); + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + [publicTenant.privateContent.id], + () => { + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + publicTenant.publicUser.restContext, + publicTenant.loggedinFolder.id, + [publicTenant.privateContent.id], + () => { + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, + [publicTenant.privateContent.id], + () => { + // Ensure a user can add only the public content item from another public tenant to a folder + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + [publicTenant1.publicContent.id], + () => { + FoldersTestUtil.assertAddContentItemsToFolderFails( + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + [publicTenant1.loggedinContent.id], + 401, + () => { + FoldersTestUtil.assertAddContentItemsToFolderFails( + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + [publicTenant1.privateContent.id], + 401, + () => { + // Ensure a user cannot add any content item from another private tenant to a folder + FoldersTestUtil.assertAddContentItemsToFolderFails( + publicTenant.publicUser + .restContext, + publicTenant.publicFolder.id, + [privateTenant.publicContent.id], + 401, + () => { + FoldersTestUtil.assertAddContentItemsToFolderFails( + publicTenant.publicUser + .restContext, + publicTenant.publicFolder.id, + [ + privateTenant + .loggedinContent.id + ], + 401, + () => { + FoldersTestUtil.assertAddContentItemsToFolderFails( + publicTenant.publicUser + .restContext, + publicTenant.publicFolder + .id, + [ + privateTenant + .privateContent.id + ], + 401, + () => { + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); }); /** @@ -5024,33 +4587,28 @@ describe('Folders', () => { assert.strictEqual(err.code, 401); // Add the link to the folder - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - camAdminRestContext, - folder.id, - [link.id], - () => { - // Ensure that Mrvisser now has access to view the link - RestAPI.Content.getContent(mrvisser.restContext, link.id, err => { - assert.ok(!err); + FoldersTestUtil.assertAddContentItemsToFolderSucceeds(camAdminRestContext, folder.id, [link.id], () => { + // Ensure that Mrvisser now has access to view the link + RestAPI.Content.getContent(mrvisser.restContext, link.id, err => { + assert.ok(!err); - // Remove the content item from the folder - FoldersTestUtil.assertRemoveContentItemsFromFolderSucceeds( - camAdminRestContext, - folder.id, - [link.id], - () => { - // Ensure that Mrvisser lost his access to view the link - RestAPI.Content.getContent(mrvisser.restContext, link.id, err => { - assert.ok(err); - assert.strictEqual(err.code, 401); + // Remove the content item from the folder + FoldersTestUtil.assertRemoveContentItemsFromFolderSucceeds( + camAdminRestContext, + folder.id, + [link.id], + () => { + // Ensure that Mrvisser lost his access to view the link + RestAPI.Content.getContent(mrvisser.restContext, link.id, err => { + assert.ok(err); + assert.strictEqual(err.code, 401); - return callback(); - }); - } - ); - }); - } - ); + return callback(); + }); + } + ); + }); + }); }); } ); @@ -5120,43 +4678,31 @@ describe('Folders', () => { [link.id], () => { // Ensure mrvisser now has access by permission chain: link -> folder -> group1 -> group2 -> group3 -> mrvisser - RestAPI.Content.getContent( - mrvisser.restContext, - link.id, - err => { - assert.ok(!err); + RestAPI.Content.getContent(mrvisser.restContext, link.id, err => { + assert.ok(!err); - // Sanity check that the reverse chain did not propagate access to simong - RestAPI.Content.getContent( - simong.restContext, - link.id, - err => { - assert.ok(err); - assert.strictEqual(err.code, 401); - - // Remove the link from the folder - FoldersTestUtil.assertRemoveContentItemsFromFolderSucceeds( - camAdminRestContext, - folder.id, - [link.id], - () => { - // Ensure that Mrvisser lost his access to view the link - RestAPI.Content.getContent( - mrvisser.restContext, - link.id, - err => { - assert.ok(err); - assert.strictEqual(err.code, 401); + // Sanity check that the reverse chain did not propagate access to simong + RestAPI.Content.getContent(simong.restContext, link.id, err => { + assert.ok(err); + assert.strictEqual(err.code, 401); - return callback(); - } - ); - } - ); + // Remove the link from the folder + FoldersTestUtil.assertRemoveContentItemsFromFolderSucceeds( + camAdminRestContext, + folder.id, + [link.id], + () => { + // Ensure that Mrvisser lost his access to view the link + RestAPI.Content.getContent(mrvisser.restContext, link.id, err => { + assert.ok(err); + assert.strictEqual(err.code, 401); + + return callback(); + }); } ); - } - ); + }); + }); } ); }); @@ -5198,20 +4744,15 @@ describe('Folders', () => { assert.ok(!err); // Ensure Mrvisser can add the item to his folder - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - mrvisser.restContext, - folder.id, - [link.id], - () => { - // Ensure Mrvisser can remove the item from his folder - return FoldersTestUtil.assertRemoveContentItemsFromFolderSucceeds( - mrvisser.restContext, - folder.id, - [link.id], - callback - ); - } - ); + FoldersTestUtil.assertAddContentItemsToFolderSucceeds(mrvisser.restContext, folder.id, [link.id], () => { + // Ensure Mrvisser can remove the item from his folder + return FoldersTestUtil.assertRemoveContentItemsFromFolderSucceeds( + mrvisser.restContext, + folder.id, + [link.id], + callback + ); + }); } ); }); @@ -5338,11 +4879,7 @@ describe('Folders', () => { FoldersTestUtil.assertAddContentItemsToFolderSucceeds( publicTenant.adminRestContext, publicTenant.publicFolder.id, - [ - publicTenant.publicContent.id, - publicTenant.loggedinContent.id, - publicTenant.privateContent.id - ], + [publicTenant.publicContent.id, publicTenant.loggedinContent.id, publicTenant.privateContent.id], () => { // Ensure anonymous, regular user, regular user from another tenant, admin from another tenant all cannot add a content item to the folder FoldersTestUtil.assertRemoveContentItemsFromFolderFails( @@ -5378,10 +4915,7 @@ describe('Folders', () => { assert.strictEqual(contentItems.length, 3); // Add public user as a manager of the folder and remove admin as a manager - const memberUpdates = _memberUpdate( - publicTenant.publicUser, - 'manager' - ); + const memberUpdates = _memberUpdate(publicTenant.publicUser, 'manager'); memberUpdates[publicTenantAdminMe.id] = false; FoldersTestUtil.assertUpdateFolderMembersSucceeds( publicTenant.adminRestContext, @@ -5440,95 +4974,90 @@ describe('Folders', () => { it('verify get folder content library authorization', callback => { TestsUtil.generateTestUsers(camAdminRestContext, 2, (err, users, simong, nico) => { assert.ok(!err); - FoldersTestUtil.generateTestFoldersWithVisibility( - simong.restContext, - 1, - 'private', - folder => { - // Create some content and add it to the folder - RestAPI.Content.createLink( - simong.restContext, - 'test', - 'test', - 'public', - 'http://www.google.ca', - null, - [], - [folder.id], - (err, link1) => { - assert.ok(!err); - RestAPI.Content.createLink( - simong.restContext, - 'test', - 'test', - 'public', - 'http://www.google.ca', - null, - [], - [folder.id], - (err, link2) => { - assert.ok(!err); - RestAPI.Content.createLink( - simong.restContext, - 'test', - 'test', - 'public', - 'http://www.google.ca', - null, - [], - [folder.id], - (err, link3) => { - assert.ok(!err); + FoldersTestUtil.generateTestFoldersWithVisibility(simong.restContext, 1, 'private', folder => { + // Create some content and add it to the folder + RestAPI.Content.createLink( + simong.restContext, + 'test', + 'test', + 'public', + 'http://www.google.ca', + null, + [], + [folder.id], + (err, link1) => { + assert.ok(!err); + RestAPI.Content.createLink( + simong.restContext, + 'test', + 'test', + 'public', + 'http://www.google.ca', + null, + [], + [folder.id], + (err, link2) => { + assert.ok(!err); + RestAPI.Content.createLink( + simong.restContext, + 'test', + 'test', + 'public', + 'http://www.google.ca', + null, + [], + [folder.id], + (err, link3) => { + assert.ok(!err); - // Only Simon and the cambridge tenant admin should be able to view the folder's content library - FoldersTestUtil.assertGetFolderContentLibraryFails( - camAnonymousRestContext, - folder.id, - null, - null, - 401, - () => { - FoldersTestUtil.assertGetFolderContentLibraryFails( - nico.restContext, - folder.id, - null, - null, - 401, - () => { - FoldersTestUtil.assertGetFolderContentLibraryFails( - gtAdminRestContext, - folder.id, - null, - null, - 401, - () => { - FoldersTestUtil.assertFolderEquals( - simong.restContext, - folder.id, - [link1.id, link2.id, link3.id], - () => { - FoldersTestUtil.assertFolderEquals( - camAdminRestContext, - folder.id, - [link1.id, link2.id, link3.id], - callback - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + // Only Simon and the cambridge tenant admin should be able to view the folder's content library + FoldersTestUtil.assertGetFolderContentLibraryFails( + camAnonymousRestContext, + folder.id, + null, + null, + 401, + () => { + FoldersTestUtil.assertGetFolderContentLibraryFails( + nico.restContext, + folder.id, + null, + null, + 401, + () => { + FoldersTestUtil.assertGetFolderContentLibraryFails( + gtAdminRestContext, + folder.id, + null, + null, + 401, + () => { + FoldersTestUtil.assertFolderEquals( + simong.restContext, + folder.id, + [link1.id, link2.id, link3.id], + () => { + FoldersTestUtil.assertFolderEquals( + camAdminRestContext, + folder.id, + [link1.id, link2.id, link3.id], + callback + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); }); }); @@ -5608,26 +5137,11 @@ describe('Folders', () => { // Assert that each folder contains all the content items const contentIds = [link.id, collabDoc.id, file.id]; - FoldersTestUtil.assertFolderEquals( - simong.restContext, - folder1.id, - contentIds, - () => { - FoldersTestUtil.assertFolderEquals( - simong.restContext, - folder2.id, - contentIds, - () => { - FoldersTestUtil.assertFolderEquals( - simong.restContext, - folder3.id, - contentIds, - callback - ); - } - ); - } - ); + FoldersTestUtil.assertFolderEquals(simong.restContext, folder1.id, contentIds, () => { + FoldersTestUtil.assertFolderEquals(simong.restContext, folder2.id, contentIds, () => { + FoldersTestUtil.assertFolderEquals(simong.restContext, folder3.id, contentIds, callback); + }); + }); } ); } @@ -5670,24 +5184,13 @@ describe('Folders', () => { (err, link) => { assert.ok(!err); - FoldersTestUtil.assertAddContentItemToFoldersSucceeds( - mrvisser.restContext, - folderIds, - link.id, - () => { - // Ensure we can get the members of the content item - RestAPI.Content.getMembers( - mrvisser.restContext, - link.id, - null, - null, - (err, result) => { - assert.ok(!err); - return callback(); - } - ); - } - ); + FoldersTestUtil.assertAddContentItemToFoldersSucceeds(mrvisser.restContext, folderIds, link.id, () => { + // Ensure we can get the members of the content item + RestAPI.Content.getMembers(mrvisser.restContext, link.id, null, null, (err, result) => { + assert.ok(!err); + return callback(); + }); + }); } ); }); @@ -5790,28 +5293,23 @@ describe('Folders', () => { FoldersLibrary.whenAllPurged(() => { // Only members of the folder can see the link in the folder FoldersTestUtil.assertFolderEquals( - publicTenant.publicUser - .restContext, + publicTenant.publicUser.restContext, publicTenant.publicFolder.id, [], () => { FoldersTestUtil.assertFolderEquals( - publicTenant1.publicUser - .restContext, - publicTenant.publicFolder - .id, + publicTenant1.publicUser.restContext, + publicTenant.publicFolder.id, [], () => { FoldersTestUtil.assertFolderEquals( camAnonymousRestContext, - publicTenant - .publicFolder.id, + publicTenant.publicFolder.id, [], () => { FoldersTestUtil.assertFolderEquals( simong.restContext, - publicTenant - .publicFolder.id, + publicTenant.publicFolder.id, [link.id], () => { return callback(); @@ -5913,60 +5411,53 @@ describe('Folders', () => { [link.id], () => { // Delete the link - RestAPI.Content.deleteContent( - publicTenant.adminRestContext, - link.id, - err => { - assert.ok(!err); + RestAPI.Content.deleteContent(publicTenant.adminRestContext, link.id, err => { + assert.ok(!err); - FoldersLibrary.whenAllPurged(() => { - // It should be removed from all the libraries - FoldersTestUtil.assertFolderEquals( - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - [], - () => { - FoldersTestUtil.assertFolderEquals( - publicTenant1.publicUser.restContext, - publicTenant.publicFolder.id, - [], - () => { - FoldersTestUtil.assertFolderEquals( - camAnonymousRestContext, - publicTenant.publicFolder.id, - [], - () => { - FoldersTestUtil.assertFolderEquals( - simong.restContext, - publicTenant.publicFolder.id, - [], - () => { - // Sanity-check it's been removed through the Library API as the REST API does it's own filtering - FoldersContentLibrary.list( - publicTenant.publicFolder, - 'public', - {}, - (err, contentIds, nextToken) => { - assert.ok(!err); - assert.strictEqual( - contentIds.length, - 0 - ); - assert.ok(!nextToken); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - }); - } - ); + FoldersLibrary.whenAllPurged(() => { + // It should be removed from all the libraries + FoldersTestUtil.assertFolderEquals( + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + [], + () => { + FoldersTestUtil.assertFolderEquals( + publicTenant1.publicUser.restContext, + publicTenant.publicFolder.id, + [], + () => { + FoldersTestUtil.assertFolderEquals( + camAnonymousRestContext, + publicTenant.publicFolder.id, + [], + () => { + FoldersTestUtil.assertFolderEquals( + simong.restContext, + publicTenant.publicFolder.id, + [], + () => { + // Sanity-check it's been removed through the Library API as the REST API does it's own filtering + FoldersContentLibrary.list( + publicTenant.publicFolder, + 'public', + {}, + (err, contentIds, nextToken) => { + assert.ok(!err); + assert.strictEqual(contentIds.length, 0); + assert.ok(!nextToken); + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + }); + }); } ); } diff --git a/packages/oae-folders/tests/test-messages.js b/packages/oae-folders/tests/test-messages.js index 9b93dc1ba5..4ff2d137ce 100644 --- a/packages/oae-folders/tests/test-messages.js +++ b/packages/oae-folders/tests/test-messages.js @@ -13,16 +13,15 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const _ = require('underscore'); +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import _ from 'underscore'; -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); - -const FoldersDAO = require('oae-folders/lib/internal/dao'); -const FoldersTestUtil = require('oae-folders/lib/test/util'); +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as FoldersDAO from 'oae-folders/lib/internal/dao'; +import * as FoldersTestUtil from 'oae-folders/lib/test/util'; describe('Folders', () => { let camAdminRestContext = null; @@ -54,63 +53,171 @@ describe('Folders', () => { [], folder => { // Test invalid folder id - FoldersTestUtil.assertCreateMessageFails( - user1.restContext, - 'not-a-valid-id', - 'a body', - null, - 400, - () => { - // Test not existing folder id - FoldersTestUtil.assertCreateMessageFails( - user1.restContext, - 'f:foo:bar', - 'a body', - null, - 404, - () => { - // Test no body + FoldersTestUtil.assertCreateMessageFails(user1.restContext, 'not-a-valid-id', 'a body', null, 400, () => { + // Test not existing folder id + FoldersTestUtil.assertCreateMessageFails(user1.restContext, 'f:foo:bar', 'a body', null, 404, () => { + // Test no body + FoldersTestUtil.assertCreateMessageFails(user1.restContext, folder.id, null, null, 400, () => { + // Test invalid reply-to timestamp + FoldersTestUtil.assertCreateMessageFails(user1.restContext, folder.id, 'a body', 'NaN', 400, () => { + // Test non-existing reply-to timestamp FoldersTestUtil.assertCreateMessageFails( user1.restContext, folder.id, - null, - null, + 'a body', + Date.now(), 400, () => { - // Test invalid reply-to timestamp - FoldersTestUtil.assertCreateMessageFails( - user1.restContext, - folder.id, - 'a body', - 'NaN', - 400, - () => { - // Test non-existing reply-to timestamp + // Test a body that is longer than the maximum allowed size + const body = TestsUtil.generateRandomText(10000); + FoldersTestUtil.assertCreateMessageFails(user1.restContext, folder.id, body, null, 400, () => { + // Sanity check + FoldersTestUtil.assertCreateMessageSucceeds( + user1.restContext, + folder.id, + 'a body', + null, + message => { + assert.ok(message); + return callback(); + } + ); + }); + } + ); + }); + }); + }); + }); + } + ); + }); + }); + + /** + * Test that verifies the model of created messages, and permissions of creating messages on different types of folders + */ + it('verify creating a message, model and permissions', callback => { + FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1, privateTenant, privateTenant1) => { + // Cannot post message as anonymous user + FoldersTestUtil.assertCreateMessageFails( + publicTenant.anonymousRestContext, + publicTenant.publicFolder.id, + 'a body', + null, + 401, + () => { + // Cannot post to private folder as non-member + FoldersTestUtil.assertCreateMessageFails( + publicTenant.privateUser.restContext, + publicTenant.privateFolder.id, + 'a body', + null, + 401, + () => { + // Can post as an authenticated user from the same tenant, verify the model + FoldersTestUtil.assertCreateMessageSucceeds( + publicTenant.publicUser.restContext, + publicTenant.publicFolder.id, + 'Top-level message', + null, + message => { + assert.ok(message); + + // This is the expected messagebox id of the folder + const messageBoxId = publicTenant.publicFolder.id; + + assert.strictEqual(message.id, messageBoxId + '#' + message.created); + assert.strictEqual(message.messageBoxId, messageBoxId); + assert.strictEqual(message.threadKey, message.created + '|'); + assert.strictEqual(message.body, 'Top-level message'); + assert.strictEqual(message.createdBy.id, publicTenant.publicUser.user.id); + assert.notStrictEqual(parseInt(message.created, 10), NaN); + assert.strictEqual(message.level, 0); + assert.ok(!message.replyTo); + + // Reply to that message and verify the model + FoldersTestUtil.assertCreateMessageSucceeds( + publicTenant.loggedinUser.restContext, + publicTenant.publicFolder.id, + 'Reply message', + message.created, + replyMessage => { + assert.ok(replyMessage); + + // This is the expected replyMessagebox id of the folder + assert.strictEqual(replyMessage.id, messageBoxId + '#' + replyMessage.created); + assert.strictEqual(replyMessage.messageBoxId, messageBoxId); + assert.strictEqual(replyMessage.threadKey, message.created + '#' + replyMessage.created + '|'); + assert.strictEqual(replyMessage.body, 'Reply message'); + assert.strictEqual(replyMessage.createdBy.id, publicTenant.loggedinUser.user.id); + assert.notStrictEqual(parseInt(replyMessage.created, 10), NaN); + assert.strictEqual(replyMessage.level, 1); + assert.ok(replyMessage.replyTo, message.created); + + // Cross-tenant user from public tenant can post to a public folder + FoldersTestUtil.assertCreateMessageSucceeds( + publicTenant1.loggedinUser.restContext, + publicTenant.publicFolder.id, + 'Message from external user', + null, + message => { + assert.ok(message); + + // Cross-tenant user from public tenant cannot post to a loggedin folder FoldersTestUtil.assertCreateMessageFails( - user1.restContext, - folder.id, - 'a body', - Date.now(), - 400, + publicTenant1.publicUser.restContext, + publicTenant.loggedinFolder.id, + 'Message from external user', + null, + 401, () => { - // Test a body that is longer than the maximum allowed size - const body = TestsUtil.generateRandomText(10000); + // Cross-tenant user from private tenant cannot post to a public folder FoldersTestUtil.assertCreateMessageFails( - user1.restContext, - folder.id, - body, + privateTenant.publicUser.restContext, + publicTenant.publicFolder.id, + 'Message from external user', null, - 400, + 401, () => { - // Sanity check - FoldersTestUtil.assertCreateMessageSucceeds( - user1.restContext, - folder.id, - 'a body', + // Cross-tenant admin cannot post to a loggedin folder + FoldersTestUtil.assertCreateMessageFails( + publicTenant1.adminRestContext, + publicTenant.loggedinFolder.id, + 'Message from external user', null, - message => { - assert.ok(message); - return callback(); + 401, + () => { + // Can post to private folder as a member. Share it, then test creating a message + FoldersTestUtil.assertShareFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + [publicTenant.privateUser], + () => { + FoldersTestUtil.assertCreateMessageSucceeds( + publicTenant.privateUser.restContext, + publicTenant.privateFolder.id, + 'Message from external user', + null, + message => { + assert.ok(message); + + // Can post to folder as admin + FoldersTestUtil.assertCreateMessageSucceeds( + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + 'Message from tenant admin user', + null, + message => { + assert.ok(message); + return callback(); + } + ); + } + ); + } + ); } ); } @@ -130,160 +237,6 @@ describe('Folders', () => { }); }); - /** - * Test that verifies the model of created messages, and permissions of creating messages on different types of folders - */ - it('verify creating a message, model and permissions', callback => { - FoldersTestUtil.setupMultiTenantPrivacyEntities( - (publicTenant, publicTenant1, privateTenant, privateTenant1) => { - // Cannot post message as anonymous user - FoldersTestUtil.assertCreateMessageFails( - publicTenant.anonymousRestContext, - publicTenant.publicFolder.id, - 'a body', - null, - 401, - () => { - // Cannot post to private folder as non-member - FoldersTestUtil.assertCreateMessageFails( - publicTenant.privateUser.restContext, - publicTenant.privateFolder.id, - 'a body', - null, - 401, - () => { - // Can post as an authenticated user from the same tenant, verify the model - FoldersTestUtil.assertCreateMessageSucceeds( - publicTenant.publicUser.restContext, - publicTenant.publicFolder.id, - 'Top-level message', - null, - message => { - assert.ok(message); - - // This is the expected messagebox id of the folder - const messageBoxId = publicTenant.publicFolder.id; - - assert.strictEqual(message.id, messageBoxId + '#' + message.created); - assert.strictEqual(message.messageBoxId, messageBoxId); - assert.strictEqual(message.threadKey, message.created + '|'); - assert.strictEqual(message.body, 'Top-level message'); - assert.strictEqual(message.createdBy.id, publicTenant.publicUser.user.id); - assert.notStrictEqual(parseInt(message.created, 10), NaN); - assert.strictEqual(message.level, 0); - assert.ok(!message.replyTo); - - // Reply to that message and verify the model - FoldersTestUtil.assertCreateMessageSucceeds( - publicTenant.loggedinUser.restContext, - publicTenant.publicFolder.id, - 'Reply message', - message.created, - replyMessage => { - assert.ok(replyMessage); - - // This is the expected replyMessagebox id of the folder - assert.strictEqual( - replyMessage.id, - messageBoxId + '#' + replyMessage.created - ); - assert.strictEqual(replyMessage.messageBoxId, messageBoxId); - assert.strictEqual( - replyMessage.threadKey, - message.created + '#' + replyMessage.created + '|' - ); - assert.strictEqual(replyMessage.body, 'Reply message'); - assert.strictEqual( - replyMessage.createdBy.id, - publicTenant.loggedinUser.user.id - ); - assert.notStrictEqual(parseInt(replyMessage.created, 10), NaN); - assert.strictEqual(replyMessage.level, 1); - assert.ok(replyMessage.replyTo, message.created); - - // Cross-tenant user from public tenant can post to a public folder - FoldersTestUtil.assertCreateMessageSucceeds( - publicTenant1.loggedinUser.restContext, - publicTenant.publicFolder.id, - 'Message from external user', - null, - message => { - assert.ok(message); - - // Cross-tenant user from public tenant cannot post to a loggedin folder - FoldersTestUtil.assertCreateMessageFails( - publicTenant1.publicUser.restContext, - publicTenant.loggedinFolder.id, - 'Message from external user', - null, - 401, - () => { - // Cross-tenant user from private tenant cannot post to a public folder - FoldersTestUtil.assertCreateMessageFails( - privateTenant.publicUser.restContext, - publicTenant.publicFolder.id, - 'Message from external user', - null, - 401, - () => { - // Cross-tenant admin cannot post to a loggedin folder - FoldersTestUtil.assertCreateMessageFails( - publicTenant1.adminRestContext, - publicTenant.loggedinFolder.id, - 'Message from external user', - null, - 401, - () => { - // Can post to private folder as a member. Share it, then test creating a message - FoldersTestUtil.assertShareFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - [publicTenant.privateUser], - () => { - FoldersTestUtil.assertCreateMessageSucceeds( - publicTenant.privateUser.restContext, - publicTenant.privateFolder.id, - 'Message from external user', - null, - message => { - assert.ok(message); - - // Can post to folder as admin - FoldersTestUtil.assertCreateMessageSucceeds( - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - 'Message from tenant admin user', - null, - message => { - assert.ok(message); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - }); - /** * Test that verifies that messages contain user profile pictures */ @@ -303,124 +256,118 @@ describe('Folders', () => { // Give one of the users a profile picture const cropArea = { x: 0, y: 0, width: 150, height: 150 }; - RestAPI.User.uploadPicture( - bert.restContext, - bert.user.id, - getPictureStream, - cropArea, - err => { - assert.ok(!err); - - // Create a folder and share it with a user that has no profile picture - FoldersTestUtil.assertCreateFolderSucceeds( - bert.restContext, - 'test displayName', - 'test description', - 'public', - [], - [nicolaas], - folder => { - // Add a message to the folder as a user with a profile picture - FoldersTestUtil.assertCreateMessageSucceeds( - bert.restContext, - folder.id, - 'Message body 1', - null, - message => { - // Assert that the picture URLs are present - assert.ok(message.createdBy); - assert.ok(message.createdBy.picture); - assert.ok(message.createdBy.picture.small); - assert.ok(message.createdBy.picture.medium); - assert.ok(message.createdBy.picture.large); - - // Assert that this works for replies as well - FoldersTestUtil.assertCreateMessageSucceeds( - bert.restContext, - folder.id, - 'Message body 2', - message.created, - reply => { - // Assert that the picture URLs are present - assert.ok(reply.createdBy); - assert.ok(reply.createdBy.picture); - assert.ok(reply.createdBy.picture.small); - assert.ok(reply.createdBy.picture.medium); - assert.ok(reply.createdBy.picture.large); - - // Add a message to the folder as a user with no profile picture - FoldersTestUtil.assertCreateMessageSucceeds( - nicolaas.restContext, - folder.id, - 'Message body 3', - null, - message => { - // Assert that no picture URLs are present - assert.ok(message.createdBy); - assert.ok(message.createdBy.picture); - assert.ok(!message.createdBy.picture.small); - assert.ok(!message.createdBy.picture.medium); - assert.ok(!message.createdBy.picture.large); - - // Assert that this works for replies as well - FoldersTestUtil.assertCreateMessageSucceeds( - nicolaas.restContext, - folder.id, - 'Message body 4', - message.created, - reply => { - // Assert that no picture URLs are present - assert.ok(reply.createdBy); - assert.ok(reply.createdBy.picture); - assert.ok(!reply.createdBy.picture.small); - assert.ok(!reply.createdBy.picture.medium); - assert.ok(!reply.createdBy.picture.large); - - // Assert the profile picture urls are present when retrieving a list of messages - FoldersTestUtil.assertGetMessagesSucceeds( - bert.restContext, - folder.id, - null, - 10, - messages => { - assert.strictEqual(messages.results.length, 4); - _.each(messages.results, message => { - assert.ok(message.createdBy); - assert.ok(message.createdBy.picture); - - // Verify that the messages have a picture for the user that - // has a profile picture - if (message.createdBy.id === bert.user.id) { - assert.ok(message.createdBy.picture.small); - assert.ok(message.createdBy.picture.medium); - assert.ok(message.createdBy.picture.large); - - // Verify that the messages don't have a picture for the user - // without a profile picture - } else if (message.createdBy.id === nicolaas.user.id) { - assert.ok(!message.createdBy.picture.small); - assert.ok(!message.createdBy.picture.medium); - assert.ok(!message.createdBy.picture.large); - } else { - assert.fail('Unexpected user in messages'); - } - }); + RestAPI.User.uploadPicture(bert.restContext, bert.user.id, getPictureStream, cropArea, err => { + assert.ok(!err); + + // Create a folder and share it with a user that has no profile picture + FoldersTestUtil.assertCreateFolderSucceeds( + bert.restContext, + 'test displayName', + 'test description', + 'public', + [], + [nicolaas], + folder => { + // Add a message to the folder as a user with a profile picture + FoldersTestUtil.assertCreateMessageSucceeds( + bert.restContext, + folder.id, + 'Message body 1', + null, + message => { + // Assert that the picture URLs are present + assert.ok(message.createdBy); + assert.ok(message.createdBy.picture); + assert.ok(message.createdBy.picture.small); + assert.ok(message.createdBy.picture.medium); + assert.ok(message.createdBy.picture.large); + + // Assert that this works for replies as well + FoldersTestUtil.assertCreateMessageSucceeds( + bert.restContext, + folder.id, + 'Message body 2', + message.created, + reply => { + // Assert that the picture URLs are present + assert.ok(reply.createdBy); + assert.ok(reply.createdBy.picture); + assert.ok(reply.createdBy.picture.small); + assert.ok(reply.createdBy.picture.medium); + assert.ok(reply.createdBy.picture.large); + + // Add a message to the folder as a user with no profile picture + FoldersTestUtil.assertCreateMessageSucceeds( + nicolaas.restContext, + folder.id, + 'Message body 3', + null, + message => { + // Assert that no picture URLs are present + assert.ok(message.createdBy); + assert.ok(message.createdBy.picture); + assert.ok(!message.createdBy.picture.small); + assert.ok(!message.createdBy.picture.medium); + assert.ok(!message.createdBy.picture.large); + + // Assert that this works for replies as well + FoldersTestUtil.assertCreateMessageSucceeds( + nicolaas.restContext, + folder.id, + 'Message body 4', + message.created, + reply => { + // Assert that no picture URLs are present + assert.ok(reply.createdBy); + assert.ok(reply.createdBy.picture); + assert.ok(!reply.createdBy.picture.small); + assert.ok(!reply.createdBy.picture.medium); + assert.ok(!reply.createdBy.picture.large); + + // Assert the profile picture urls are present when retrieving a list of messages + FoldersTestUtil.assertGetMessagesSucceeds( + bert.restContext, + folder.id, + null, + 10, + messages => { + assert.strictEqual(messages.results.length, 4); + _.each(messages.results, message => { + assert.ok(message.createdBy); + assert.ok(message.createdBy.picture); + + // Verify that the messages have a picture for the user that + // has a profile picture + if (message.createdBy.id === bert.user.id) { + assert.ok(message.createdBy.picture.small); + assert.ok(message.createdBy.picture.medium); + assert.ok(message.createdBy.picture.large); + + // Verify that the messages don't have a picture for the user + // without a profile picture + } else if (message.createdBy.id === nicolaas.user.id) { + assert.ok(!message.createdBy.picture.small); + assert.ok(!message.createdBy.picture.medium); + assert.ok(!message.createdBy.picture.large); + } else { + assert.fail('Unexpected user in messages'); + } + }); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); }); }); @@ -443,57 +390,45 @@ describe('Folders', () => { const lastModified1 = folder.lastModified; // Create a message to test with - FoldersTestUtil.assertCreateMessageSucceeds( - simong.restContext, - folder.id, - 'My message', - null, - message => { - // Ensure lastModified didn't change because it is within the one hour threshold - FoldersTestUtil.assertGetFolderSucceeds(simong.restContext, folder.id, folder => { + FoldersTestUtil.assertCreateMessageSucceeds(simong.restContext, folder.id, 'My message', null, message => { + // Ensure lastModified didn't change because it is within the one hour threshold + FoldersTestUtil.assertGetFolderSucceeds(simong.restContext, folder.id, folder => { + assert.ok(!err); + assert.strictEqual(folder.lastModified, lastModified1.toString()); + + // Force a naughty update through the DAO of the lastModified to more than an hour ago (threshold duration) + const lastModified0 = lastModified1 - 1 * 60 * 61 * 1000; + FoldersDAO.updateFolder(folder, { lastModified: lastModified0 }, (err, folder) => { assert.ok(!err); - assert.strictEqual(folder.lastModified, lastModified1.toString()); - - // Force a naughty update through the DAO of the lastModified to more than an hour ago (threshold duration) - const lastModified0 = lastModified1 - 1 * 60 * 61 * 1000; - FoldersDAO.updateFolder( - folder, - { lastModified: lastModified0 }, - (err, folder) => { - assert.ok(!err); - assert.strictEqual(folder.lastModified, lastModified0); + assert.strictEqual(folder.lastModified, lastModified0); - // Message again, this time the lastModified should update - FoldersTestUtil.assertCreateMessageSucceeds( + // Message again, this time the lastModified should update + FoldersTestUtil.assertCreateMessageSucceeds( + simong.restContext, + folder.id, + 'My second message', + null, + message => { + // Ensure the new lastModified is greater than the original creation one + setTimeout( + FoldersTestUtil.assertGetFolderSucceeds, + 200, simong.restContext, folder.id, - 'My second message', - null, - message => { - // Ensure the new lastModified is greater than the original creation one - setTimeout( - FoldersTestUtil.assertGetFolderSucceeds, - 200, - simong.restContext, - folder.id, - folder => { - assert.ok( - parseInt(folder.lastModified, 10) > parseInt(lastModified1, 10) - ); + folder => { + assert.ok(parseInt(folder.lastModified, 10) > parseInt(lastModified1, 10)); - // Note at this time, since the lastModified of the folder updated under the hood without - // a library update, the library of user should 2 versions of this folder. Lets see if it - // auto-repairs - FoldersTestUtil.assertGetFoldersLibrarySucceeds( - simong.restContext, - simong.user.id, - null, - null, - items => { - assert.strictEqual(items.results.length, 1); - return callback(); - } - ); + // Note at this time, since the lastModified of the folder updated under the hood without + // a library update, the library of user should 2 versions of this folder. Lets see if it + // auto-repairs + FoldersTestUtil.assertGetFoldersLibrarySucceeds( + simong.restContext, + simong.user.id, + null, + null, + items => { + assert.strictEqual(items.results.length, 1); + return callback(); } ); } @@ -501,8 +436,8 @@ describe('Folders', () => { } ); }); - } - ); + }); + }); } ); }); @@ -527,37 +462,17 @@ describe('Folders', () => { [], folder => { // Validate invalid folder id - FoldersTestUtil.assertGetMessagesFails( - simong.restContext, - 'not-a-valid-id', - null, - null, - 400, - () => { - // Non-existing folder - FoldersTestUtil.assertGetMessagesFails( - simong.restContext, - 'f:foo:bar', - null, - null, - 404, - () => { - // Sanity-check - FoldersTestUtil.assertGetMessagesSucceeds( - simong.restContext, - folder.id, - null, - null, - messages => { - assert.ok(!err); - assert.ok(messages); - return callback(); - } - ); - } - ); - } - ); + FoldersTestUtil.assertGetMessagesFails(simong.restContext, 'not-a-valid-id', null, null, 400, () => { + // Non-existing folder + FoldersTestUtil.assertGetMessagesFails(simong.restContext, 'f:foo:bar', null, null, 404, () => { + // Sanity-check + FoldersTestUtil.assertGetMessagesSucceeds(simong.restContext, folder.id, null, null, messages => { + assert.ok(!err); + assert.ok(messages); + return callback(); + }); + }); + }); } ); }); @@ -576,12 +491,7 @@ describe('Folders', () => { * @param {Boolean} userScrubbed Whether or not the createdBy field should have scrubbed user data * @throws {Error} Throws an assertion error if the data fails assertions */ - const _assertMessageModel = function( - messageToTest, - messageToTestAgainst, - creatorToTestAgainst, - userScrubbed - ) { + const _assertMessageModel = function(messageToTest, messageToTestAgainst, creatorToTestAgainst, userScrubbed) { // Verify message model assert.strictEqual(messageToTest.id, messageToTestAgainst.id); assert.strictEqual(messageToTest.messageBoxId, messageToTestAgainst.messageBoxId); @@ -594,10 +504,7 @@ describe('Folders', () => { // Verify creator model assert.ok(messageToTest.createdBy); assert.strictEqual(messageToTest.createdBy.tenant.alias, creatorToTestAgainst.tenant.alias); - assert.strictEqual( - messageToTest.createdBy.tenant.displayName, - creatorToTestAgainst.tenant.displayName - ); + assert.strictEqual(messageToTest.createdBy.tenant.displayName, creatorToTestAgainst.tenant.displayName); assert.strictEqual(messageToTest.createdBy.visibility, creatorToTestAgainst.visibility); // Privacy check @@ -609,223 +516,212 @@ describe('Folders', () => { }; // Set up the tenants for tenant privacy rule checking - FoldersTestUtil.setupMultiTenantPrivacyEntities( - (publicTenant, publicTenant1, privateTenant, privateTenant1) => { - // Create message structure on the public folder - FoldersTestUtil.assertCreateMessageSucceeds( - publicTenant.loggedinUser.restContext, - publicTenant.publicFolder.id, - 'Message1 parent on public', - null, - publicMessage1 => { - FoldersTestUtil.assertCreateMessageSucceeds( - publicTenant.loggedinUser.restContext, - publicTenant.publicFolder.id, - 'Message1 reply on public', - publicMessage1.created, - replyPublicMessage1 => { - FoldersTestUtil.assertCreateMessageSucceeds( - publicTenant.loggedinUser.restContext, - publicTenant.publicFolder.id, - 'Message2 parent on public', - null, - publicMessage2 => { - // Create message on the loggedin folder - FoldersTestUtil.assertCreateMessageSucceeds( - publicTenant.loggedinUser.restContext, - publicTenant.loggedinFolder.id, - 'Message on loggedin', - null, - loggedinMessage => { - // Share and post message on the private folder - FoldersTestUtil.assertShareFolderSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - [publicTenant.privateUser], - () => { - FoldersTestUtil.assertCreateMessageSucceeds( - publicTenant.privateUser.restContext, - publicTenant.privateFolder.id, - 'Message on private', - null, - privateMessage => { - // Anonymous can read on public, but not loggedin or private - FoldersTestUtil.assertGetMessagesSucceeds( - publicTenant.anonymousRestContext, - publicTenant.publicFolder.id, - null, - null, - messages => { - assert.ok(messages); - assert.strictEqual(messages.results.length, 3); - - // Verify the model of all 3 messages - _assertMessageModel( - messages.results[0], - publicMessage2, - publicTenant.loggedinUser.user, - true - ); - _assertMessageModel( - messages.results[1], - publicMessage1, - publicTenant.loggedinUser.user, - true - ); - _assertMessageModel( - messages.results[2], - replyPublicMessage1, - publicTenant.loggedinUser.user, - true - ); - - FoldersTestUtil.assertGetMessagesFails( - publicTenant.anonymousRestContext, - publicTenant.loggedinFolder.id, - null, - null, - 401, - messages => { - FoldersTestUtil.assertGetMessagesFails( - publicTenant.anonymousRestContext, - publicTenant.privateFolder.id, - null, - null, - 401, - messages => { - // Authenticated user can read loggedin - FoldersTestUtil.assertGetMessagesSucceeds( - publicTenant.publicUser.restContext, - publicTenant.loggedinFolder.id, - null, - null, - messages => { - assert.ok(messages); - assert.strictEqual(messages.results.length, 1); - - // Verify the model of the message, the loggedin user should not be scrubbed - _assertMessageModel( - messages.results[0], - loggedinMessage, - publicTenant.loggedinUser.user, - false - ); - - // Authenticated user cannot read private - FoldersTestUtil.assertGetMessagesFails( - publicTenant.publicUser.restContext, - publicTenant.privateFolder.id, - null, - null, - 401, - messages => { - // Member user can read private - FoldersTestUtil.assertGetMessagesSucceeds( - publicTenant.privateUser.restContext, - publicTenant.privateFolder.id, - null, - null, - messages => { - assert.ok(messages); - assert.strictEqual( - messages.results.length, - 1 - ); - - // Verify the model of the message, the loggedin user should not be scrubbed - _assertMessageModel( - messages.results[0], - privateMessage, - publicTenant.privateUser.user, - false - ); - - // Ensure paging of the messages - FoldersTestUtil.assertGetMessagesSucceeds( - publicTenant.anonymousRestContext, - publicTenant.publicFolder.id, - null, - 2, - messages => { - assert.ok(messages); - assert.strictEqual( - messages.nextToken, - messages.results[1].threadKey - ); - - assert.strictEqual( - messages.results.length, - 2 - ); - - // Verify the model and ordering of the messages - _assertMessageModel( - messages.results[0], - publicMessage2, - publicTenant.loggedinUser.user, - true - ); - _assertMessageModel( - messages.results[1], - publicMessage1, - publicTenant.loggedinUser.user, - true - ); - - // Try and get 2 more. Should only get 1 and it should be the 3rd message - FoldersTestUtil.assertGetMessagesSucceeds( - publicTenant.anonymousRestContext, - publicTenant.publicFolder.id, - publicMessage1.threadKey, - 2, - messages => { - assert.ok(messages); - assert.strictEqual( - messages.results.length, - 1 - ); - assert.ok(!messages.nextToken); - - // Verify the model and ordering of the messages - _assertMessageModel( - messages.results[0], - replyPublicMessage1, - publicTenant.loggedinUser.user, - true - ); + FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1, privateTenant, privateTenant1) => { + // Create message structure on the public folder + FoldersTestUtil.assertCreateMessageSucceeds( + publicTenant.loggedinUser.restContext, + publicTenant.publicFolder.id, + 'Message1 parent on public', + null, + publicMessage1 => { + FoldersTestUtil.assertCreateMessageSucceeds( + publicTenant.loggedinUser.restContext, + publicTenant.publicFolder.id, + 'Message1 reply on public', + publicMessage1.created, + replyPublicMessage1 => { + FoldersTestUtil.assertCreateMessageSucceeds( + publicTenant.loggedinUser.restContext, + publicTenant.publicFolder.id, + 'Message2 parent on public', + null, + publicMessage2 => { + // Create message on the loggedin folder + FoldersTestUtil.assertCreateMessageSucceeds( + publicTenant.loggedinUser.restContext, + publicTenant.loggedinFolder.id, + 'Message on loggedin', + null, + loggedinMessage => { + // Share and post message on the private folder + FoldersTestUtil.assertShareFolderSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + [publicTenant.privateUser], + () => { + FoldersTestUtil.assertCreateMessageSucceeds( + publicTenant.privateUser.restContext, + publicTenant.privateFolder.id, + 'Message on private', + null, + privateMessage => { + // Anonymous can read on public, but not loggedin or private + FoldersTestUtil.assertGetMessagesSucceeds( + publicTenant.anonymousRestContext, + publicTenant.publicFolder.id, + null, + null, + messages => { + assert.ok(messages); + assert.strictEqual(messages.results.length, 3); + + // Verify the model of all 3 messages + _assertMessageModel( + messages.results[0], + publicMessage2, + publicTenant.loggedinUser.user, + true + ); + _assertMessageModel( + messages.results[1], + publicMessage1, + publicTenant.loggedinUser.user, + true + ); + _assertMessageModel( + messages.results[2], + replyPublicMessage1, + publicTenant.loggedinUser.user, + true + ); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + FoldersTestUtil.assertGetMessagesFails( + publicTenant.anonymousRestContext, + publicTenant.loggedinFolder.id, + null, + null, + 401, + messages => { + FoldersTestUtil.assertGetMessagesFails( + publicTenant.anonymousRestContext, + publicTenant.privateFolder.id, + null, + null, + 401, + messages => { + // Authenticated user can read loggedin + FoldersTestUtil.assertGetMessagesSucceeds( + publicTenant.publicUser.restContext, + publicTenant.loggedinFolder.id, + null, + null, + messages => { + assert.ok(messages); + assert.strictEqual(messages.results.length, 1); + + // Verify the model of the message, the loggedin user should not be scrubbed + _assertMessageModel( + messages.results[0], + loggedinMessage, + publicTenant.loggedinUser.user, + false + ); + + // Authenticated user cannot read private + FoldersTestUtil.assertGetMessagesFails( + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, + null, + null, + 401, + messages => { + // Member user can read private + FoldersTestUtil.assertGetMessagesSucceeds( + publicTenant.privateUser.restContext, + publicTenant.privateFolder.id, + null, + null, + messages => { + assert.ok(messages); + assert.strictEqual(messages.results.length, 1); + + // Verify the model of the message, the loggedin user should not be scrubbed + _assertMessageModel( + messages.results[0], + privateMessage, + publicTenant.privateUser.user, + false + ); + + // Ensure paging of the messages + FoldersTestUtil.assertGetMessagesSucceeds( + publicTenant.anonymousRestContext, + publicTenant.publicFolder.id, + null, + 2, + messages => { + assert.ok(messages); + assert.strictEqual( + messages.nextToken, + messages.results[1].threadKey + ); + + assert.strictEqual(messages.results.length, 2); + + // Verify the model and ordering of the messages + _assertMessageModel( + messages.results[0], + publicMessage2, + publicTenant.loggedinUser.user, + true + ); + _assertMessageModel( + messages.results[1], + publicMessage1, + publicTenant.loggedinUser.user, + true + ); + + // Try and get 2 more. Should only get 1 and it should be the 3rd message + FoldersTestUtil.assertGetMessagesSucceeds( + publicTenant.anonymousRestContext, + publicTenant.publicFolder.id, + publicMessage1.threadKey, + 2, + messages => { + assert.ok(messages); + assert.strictEqual(messages.results.length, 1); + assert.ok(!messages.nextToken); + + // Verify the model and ordering of the messages + _assertMessageModel( + messages.results[0], + replyPublicMessage1, + publicTenant.loggedinUser.user, + true + ); + + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); }); }); @@ -847,48 +743,183 @@ describe('Folders', () => { [], folder => { // Create message on the folder to delete - FoldersTestUtil.assertCreateMessageSucceeds( - simong.restContext, - folder.id, - 'a message', - null, - message => { - // Validate invalid folder id - FoldersTestUtil.assertDeleteMessageFails( - simong.restContext, - 'not-a-folder-id', - message.created, - 400, - () => { - // Unknown folder id - FoldersTestUtil.assertDeleteMessageFails( - simong.restContext, - 'f:foo:bar', - message.created, - 404, - () => { - // Validate invalid timestamp + FoldersTestUtil.assertCreateMessageSucceeds(simong.restContext, folder.id, 'a message', null, message => { + // Validate invalid folder id + FoldersTestUtil.assertDeleteMessageFails( + simong.restContext, + 'not-a-folder-id', + message.created, + 400, + () => { + // Unknown folder id + FoldersTestUtil.assertDeleteMessageFails( + simong.restContext, + 'f:foo:bar', + message.created, + 404, + () => { + // Validate invalid timestamp + FoldersTestUtil.assertDeleteMessageFails(simong.restContext, folder.id, 'NaN', 400, () => { FoldersTestUtil.assertDeleteMessageFails( simong.restContext, folder.id, - 'NaN', + 'Not a created timestamp', 400, () => { - FoldersTestUtil.assertDeleteMessageFails( + // Assert the message was not removed + FoldersTestUtil.assertGetMessagesSucceeds( simong.restContext, folder.id, - 'Not a created timestamp', - 400, + null, + 2, + messages => { + assert.strictEqual(messages.results.length, 1); + return callback(); + } + ); + } + ); + }); + } + ); + } + ); + }); + } + ); + }); + }); + + /** + * Test that verifies the logic of deleting messages, and the model and permissions for the operation + */ + it('verify deleting messages, model and permissions', callback => { + FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant, publicTenant1, privateTenant, privateTenant1) => { + // Add a manager to the folder + const updates = {}; + updates[publicTenant.privateUser.user.id] = 'manager'; + updates[publicTenant.loggedinUser.user.id] = 'viewer'; + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + publicTenant.adminRestContext, + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + updates, + () => { + // Create message structure on the public folder + FoldersTestUtil.assertCreateMessageSucceeds( + publicTenant.loggedinUser.restContext, + publicTenant.privateFolder.id, + 'Message1 parent on public', + null, + publicMessage1 => { + FoldersTestUtil.assertCreateMessageSucceeds( + publicTenant.loggedinUser.restContext, + publicTenant.privateFolder.id, + 'Message1 reply on public', + publicMessage1.created, + replyPublicMessage1 => { + FoldersTestUtil.assertCreateMessageSucceeds( + publicTenant.loggedinUser.restContext, + publicTenant.privateFolder.id, + 'Message2 parent on public', + null, + publicMessage2 => { + // Verify anonymous cannot delete a message + FoldersTestUtil.assertDeleteMessageFails( + publicTenant.anonymousRestContext, + publicTenant.privateFolder.id, + publicMessage1.created, + 401, + () => { + // Verify non-manager, non-creator user can't delete a message + FoldersTestUtil.assertDeleteMessageFails( + publicTenant.publicUser.restContext, + publicTenant.privateFolder.id, + publicMessage1.created, + 401, () => { - // Assert the message was not removed - FoldersTestUtil.assertGetMessagesSucceeds( - simong.restContext, - folder.id, - null, - 2, - messages => { - assert.strictEqual(messages.results.length, 1); - return callback(); + // Verify manager can delete, also verify the parent message is soft-deleted and its model + FoldersTestUtil.assertDeleteMessageSucceeds( + publicTenant.privateUser.restContext, + publicTenant.privateFolder.id, + publicMessage1.created, + message => { + // Ensure the deleted message model + assert.strictEqual(message.id, publicMessage1.id); + assert.strictEqual(message.messageBoxId, publicMessage1.messageBoxId); + assert.strictEqual(message.threadKey, publicMessage1.threadKey); + assert.strictEqual(message.created, publicMessage1.created); + assert.strictEqual(message.replyTo, publicMessage1.replyTo); + assert.notStrictEqual(parseInt(message.deleted, 10), NaN); + assert.ok(parseInt(message.deleted, 10) > parseInt(message.created, 10)); + assert.strictEqual(message.level, publicMessage1.level); + assert.ok(!message.body); + assert.ok(!message.createdBy); + + // Ensure the deleted message is still in the list of messages, but marked as deleted + FoldersTestUtil.assertGetMessagesSucceeds( + publicTenant.privateUser.restContext, + publicTenant.privateFolder.id, + null, + null, + items => { + assert.ok(items.results.length, 3); + + const message = items.results[1]; + assert.strictEqual(message.id, publicMessage1.id); + assert.strictEqual(message.messageBoxId, publicMessage1.messageBoxId); + assert.strictEqual(message.threadKey, publicMessage1.threadKey); + assert.strictEqual(message.created, publicMessage1.created); + assert.strictEqual(message.replyTo, publicMessage1.replyTo); + assert.notStrictEqual(parseInt(message.deleted, 10), NaN); + assert.ok(parseInt(message.deleted, 10) > parseInt(message.created, 10)); + assert.strictEqual(message.level, publicMessage1.level); + assert.ok(!message.body); + assert.ok(!message.createdBy); + + // Delete the rest of the messages to test hard-deletes. This also tests owner can delete + FoldersTestUtil.assertDeleteMessageSucceeds( + publicTenant.loggedinUser.restContext, + publicTenant.privateFolder.id, + replyPublicMessage1.created, + message => { + assert.ok(!message); + + // We re-delete this one, but it should actually do a hard delete this time as there are no children + FoldersTestUtil.assertDeleteMessageSucceeds( + publicTenant.loggedinUser.restContext, + publicTenant.privateFolder.id, + publicMessage1.created, + message => { + assert.ok(!message); + + // Perform a hard-delete on this leaf message. This also tests admin can delete + FoldersTestUtil.assertDeleteMessageSucceeds( + publicTenant.adminRestContext, + publicTenant.privateFolder.id, + publicMessage2.created, + message => { + assert.ok(!message); + + // Should be no more messages in the folder as they should have all been de-indexed by hard deletes + FoldersTestUtil.assertGetMessagesSucceeds( + publicTenant.privateUser.restContext, + publicTenant.privateFolder.id, + null, + null, + items => { + assert.strictEqual(items.results.length, 0); + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); } ); } @@ -905,181 +936,5 @@ describe('Folders', () => { ); }); }); - - /** - * Test that verifies the logic of deleting messages, and the model and permissions for the operation - */ - it('verify deleting messages, model and permissions', callback => { - FoldersTestUtil.setupMultiTenantPrivacyEntities( - (publicTenant, publicTenant1, privateTenant, privateTenant1) => { - // Add a manager to the folder - const updates = {}; - updates[publicTenant.privateUser.user.id] = 'manager'; - updates[publicTenant.loggedinUser.user.id] = 'viewer'; - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - publicTenant.adminRestContext, - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - updates, - () => { - // Create message structure on the public folder - FoldersTestUtil.assertCreateMessageSucceeds( - publicTenant.loggedinUser.restContext, - publicTenant.privateFolder.id, - 'Message1 parent on public', - null, - publicMessage1 => { - FoldersTestUtil.assertCreateMessageSucceeds( - publicTenant.loggedinUser.restContext, - publicTenant.privateFolder.id, - 'Message1 reply on public', - publicMessage1.created, - replyPublicMessage1 => { - FoldersTestUtil.assertCreateMessageSucceeds( - publicTenant.loggedinUser.restContext, - publicTenant.privateFolder.id, - 'Message2 parent on public', - null, - publicMessage2 => { - // Verify anonymous cannot delete a message - FoldersTestUtil.assertDeleteMessageFails( - publicTenant.anonymousRestContext, - publicTenant.privateFolder.id, - publicMessage1.created, - 401, - () => { - // Verify non-manager, non-creator user can't delete a message - FoldersTestUtil.assertDeleteMessageFails( - publicTenant.publicUser.restContext, - publicTenant.privateFolder.id, - publicMessage1.created, - 401, - () => { - // Verify manager can delete, also verify the parent message is soft-deleted and its model - FoldersTestUtil.assertDeleteMessageSucceeds( - publicTenant.privateUser.restContext, - publicTenant.privateFolder.id, - publicMessage1.created, - message => { - // Ensure the deleted message model - assert.strictEqual(message.id, publicMessage1.id); - assert.strictEqual( - message.messageBoxId, - publicMessage1.messageBoxId - ); - assert.strictEqual( - message.threadKey, - publicMessage1.threadKey - ); - assert.strictEqual(message.created, publicMessage1.created); - assert.strictEqual(message.replyTo, publicMessage1.replyTo); - assert.notStrictEqual(parseInt(message.deleted, 10), NaN); - assert.ok( - parseInt(message.deleted, 10) > - parseInt(message.created, 10) - ); - assert.strictEqual(message.level, publicMessage1.level); - assert.ok(!message.body); - assert.ok(!message.createdBy); - - // Ensure the deleted message is still in the list of messages, but marked as deleted - FoldersTestUtil.assertGetMessagesSucceeds( - publicTenant.privateUser.restContext, - publicTenant.privateFolder.id, - null, - null, - items => { - assert.ok(items.results.length, 3); - - const message = items.results[1]; - assert.strictEqual(message.id, publicMessage1.id); - assert.strictEqual( - message.messageBoxId, - publicMessage1.messageBoxId - ); - assert.strictEqual( - message.threadKey, - publicMessage1.threadKey - ); - assert.strictEqual( - message.created, - publicMessage1.created - ); - assert.strictEqual( - message.replyTo, - publicMessage1.replyTo - ); - assert.notStrictEqual(parseInt(message.deleted, 10), NaN); - assert.ok( - parseInt(message.deleted, 10) > - parseInt(message.created, 10) - ); - assert.strictEqual(message.level, publicMessage1.level); - assert.ok(!message.body); - assert.ok(!message.createdBy); - - // Delete the rest of the messages to test hard-deletes. This also tests owner can delete - FoldersTestUtil.assertDeleteMessageSucceeds( - publicTenant.loggedinUser.restContext, - publicTenant.privateFolder.id, - replyPublicMessage1.created, - message => { - assert.ok(!message); - - // We re-delete this one, but it should actually do a hard delete this time as there are no children - FoldersTestUtil.assertDeleteMessageSucceeds( - publicTenant.loggedinUser.restContext, - publicTenant.privateFolder.id, - publicMessage1.created, - message => { - assert.ok(!message); - - // Perform a hard-delete on this leaf message. This also tests admin can delete - FoldersTestUtil.assertDeleteMessageSucceeds( - publicTenant.adminRestContext, - publicTenant.privateFolder.id, - publicMessage2.created, - message => { - assert.ok(!message); - - // Should be no more messages in the folder as they should have all been de-indexed by hard deletes - FoldersTestUtil.assertGetMessagesSucceeds( - publicTenant.privateUser.restContext, - publicTenant.privateFolder.id, - null, - null, - items => { - assert.strictEqual( - items.results.length, - 0 - ); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - }); }); }); diff --git a/packages/oae-folders/tests/test-push.js b/packages/oae-folders/tests/test-push.js index 9bbabca24e..30cf5efeae 100644 --- a/packages/oae-folders/tests/test-push.js +++ b/packages/oae-folders/tests/test-push.js @@ -13,18 +13,16 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityTestsUtil = require('oae-activity/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const TestsUtil = require('oae-tests'); +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as ActivityTestsUtil from 'oae-activity/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; -const { FoldersConstants } = require('oae-folders/lib/constants'); -const FoldersTestUtil = require('oae-folders/lib/test/util'); +import { FoldersConstants } from 'oae-folders/lib/constants'; +import * as FoldersTestUtil from 'oae-folders/lib/test/util'; describe('Folders - Push', () => { // Rest contexts that can be used performing rest requests @@ -34,9 +32,7 @@ describe('Folders - Push', () => { * Function that will fill up the tenant admin and anymous rest contexts */ before(callback => { - localAdminRestContext = TestsUtil.createTenantAdminRestContext( - global.oaeTests.tenants.localhost.host - ); + localAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.localhost.host); callback(); }); @@ -80,64 +76,40 @@ describe('Folders - Push', () => { assert.strictEqual(err.code, 400); // Ensure we get a 400 error with an invalid token - client.subscribe( - folder.id, - 'activity', - { signature: folder.signature.signature }, - null, - err => { + client.subscribe(folder.id, 'activity', { signature: folder.signature.signature }, null, err => { + assert.strictEqual(err.code, 401); + client.subscribe(folder.id, 'activity', { expires: folder.signature.expires }, null, err => { assert.strictEqual(err.code, 401); + + // Ensure we get a 401 error with an incorrect signature client.subscribe( folder.id, 'activity', - { expires: folder.signature.expires }, + { expires: Date.now() + 10000, signature: 'foo' }, null, err => { assert.strictEqual(err.code, 401); - // Ensure we get a 401 error with an incorrect signature - client.subscribe( + // Simon should not be able to use a signature that was generated for Branden + FoldersTestUtil.assertGetFolderSucceeds( + branden.restContext, folder.id, - 'activity', - { expires: Date.now() + 10000, signature: 'foo' }, - null, - err => { - assert.strictEqual(err.code, 401); - - // Simon should not be able to use a signature that was generated for Branden - FoldersTestUtil.assertGetFolderSucceeds( - branden.restContext, - folder.id, - folderForBranden => { - client.subscribe( - folder.id, - 'activity', - folderForBranden.signature, - null, - err => { - assert.strictEqual(err.code, 401); - - // Sanity check that a valid signature works - client.subscribe( - folder.id, - 'activity', - folder.signature, - null, - err => { - assert.ok(!err); - return callback(); - } - ); - } - ); - } - ); + folderForBranden => { + client.subscribe(folder.id, 'activity', folderForBranden.signature, null, err => { + assert.strictEqual(err.code, 401); + + // Sanity check that a valid signature works + client.subscribe(folder.id, 'activity', folder.signature, null, err => { + assert.ok(!err); + return callback(); + }); + }); } ); } ); - } - ); + }); + }); }); }); }); @@ -259,19 +231,15 @@ describe('Folders - Push', () => { it('verify visibility updates trigger a push notification', callback => { setupFixture((contexts, folder, client) => { // Trigger an update - RestAPI.Folders.updateFolder( - contexts.branden.restContext, - folder.id, - { visibility: 'loggedin' }, - err => { - assert.ok(!err); - } - ); + RestAPI.Folders.updateFolder(contexts.branden.restContext, folder.id, { visibility: 'loggedin' }, err => { + assert.ok(!err); + }); client.on('message', message => { if (isMessageFromFolderCreation(message)) { return; } + ActivityTestsUtil.assertActivity( message.activities[0], FoldersConstants.activity.ACTIVITY_FOLDER_UPDATE_VISIBILITY, @@ -312,6 +280,7 @@ describe('Folders - Push', () => { if (isMessageFromFolderCreation(message)) { return; } + if (seenFirstComment) { // This should be the reply message. Because comments are delivered to websockets // on routing, this message will not aggregate with the previous activity. It should diff --git a/packages/oae-folders/tests/test-search.js b/packages/oae-folders/tests/test-search.js index 65f3257777..d58566c207 100644 --- a/packages/oae-folders/tests/test-search.js +++ b/packages/oae-folders/tests/test-search.js @@ -13,20 +13,16 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const util = require('util'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const ConfigTestUtil = require('oae-config/lib/test/util'); -const RestAPI = require('oae-rest'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); - -const FoldersAPI = require('oae-folders'); -const { FoldersConstants } = require('oae-folders/lib/constants'); -const FoldersDAO = require('oae-folders/lib/internal/dao'); -const FoldersLibrary = require('oae-folders/lib/library'); -const FoldersTestUtil = require('oae-folders/lib/test/util'); +import * as RestAPI from 'oae-rest'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests'; +import * as FoldersAPI from 'oae-folders'; +import * as FoldersDAO from 'oae-folders/lib/internal/dao'; +import * as FoldersTestUtil from 'oae-folders/lib/test/util'; +import { FoldersConstants } from 'oae-folders/lib/constants'; describe('Folders', () => { let camAdminRestContext = null; @@ -36,9 +32,9 @@ describe('Folders', () => { let gtAnonymousRestContext = null; /*! - * Set up all the REST contexts for admin and anonymous users with which we - * will invoke requests - */ + * Set up all the REST contexts for admin and anonymous users with which we + * will invoke requests + */ before(callback => { camAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.cam.host); camAnonymousRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); @@ -67,69 +63,62 @@ describe('Folders', () => { * @throws {AssertionError} Throws an error if anything unexpected happens when setting up the entities */ const _setup = function(visibility, callback) { - FoldersTestUtil.setupMultiTenantPrivacyEntities( - (publicTenant1, publicTenant2, privateTenant, privateTenant1) => { - // Create a test user who will generate a test folder - TestsUtil.generateTestUsers(publicTenant1.adminRestContext, 1, (err, users, simong) => { - assert.ok(!err); - FoldersTestUtil.generateTestFoldersWithVisibility( + FoldersTestUtil.setupMultiTenantPrivacyEntities((publicTenant1, publicTenant2, privateTenant, privateTenant1) => { + // Create a test user who will generate a test folder + TestsUtil.generateTestUsers(publicTenant1.adminRestContext, 1, (err, users, simong) => { + assert.ok(!err); + FoldersTestUtil.generateTestFoldersWithVisibility(simong.restContext, 1, visibility, folder => { + // Create 3 content items + RestAPI.Content.createLink( simong.restContext, - 1, - visibility, - folder => { - // Create 3 content items + 'public', + 'public', + 'public', + 'http://www.google.com', + null, + [], + [], + (err, publicContent) => { + assert.ok(!err); RestAPI.Content.createLink( simong.restContext, - 'public', - 'public', - 'public', + 'loggedin', + 'loggedin', + 'loggedin', 'http://www.google.com', null, [], [], - (err, publicContent) => { + (err, loggedinContent) => { assert.ok(!err); RestAPI.Content.createLink( simong.restContext, - 'loggedin', - 'loggedin', - 'loggedin', + 'private', + 'private', + 'private', 'http://www.google.com', null, [], [], - (err, loggedinContent) => { + (err, privateContent) => { assert.ok(!err); - RestAPI.Content.createLink( - simong.restContext, - 'private', - 'private', - 'private', - 'http://www.google.com', - null, - [], - [], - (err, privateContent) => { - assert.ok(!err); - // Add them to the folder - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - simong.restContext, - folder.id, - [publicContent.id, loggedinContent.id, privateContent.id], - () => { - return callback( - simong, - folder, - publicContent, - loggedinContent, - privateContent, - publicTenant1, - publicTenant2, - privateTenant, - privateTenant1 - ); - } + // Add them to the folder + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + simong.restContext, + folder.id, + [publicContent.id, loggedinContent.id, privateContent.id], + () => { + return callback( + simong, + folder, + publicContent, + loggedinContent, + privateContent, + publicTenant1, + publicTenant2, + privateTenant, + privateTenant1 ); } ); @@ -140,8 +129,8 @@ describe('Folders', () => { } ); }); - } - ); + }); + }); }; describe('Searching in folders', () => { @@ -156,76 +145,64 @@ describe('Folders', () => { FoldersTestUtil.generateTestFolders(simong.restContext, 2, (folder1, folder2) => { // Both folders should be empty FoldersTestUtil.assertFolderSearchEquals(simong.restContext, folder1.id, null, [], () => { - FoldersTestUtil.assertFolderSearchEquals( - simong.restContext, - folder2.id, - null, - [], - () => { - // Create some content items and add them to the first folder - RestAPI.Content.createLink( - camAdminRestContext, - 'test', - 'test', - 'public', - 'http://www.google.com', - null, - [], - [], - (err, google) => { - assert.ok(!err); - RestAPI.Content.createLink( - camAdminRestContext, - 'marsupilamisausage', - 'marsupilamisausage', - 'public', - 'http://www.marsupilamisausage.com', - null, - [], - [], - (err, mars) => { - assert.ok(!err); - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - simong.restContext, - folder1.id, - [google.id, mars.id], - () => { - // Searching through the first folder should give the - // first 2 links. The other folder should still be empty - FoldersTestUtil.assertFolderSearchEquals( - simong.restContext, - folder1.id, - null, - [google, mars], - () => { + FoldersTestUtil.assertFolderSearchEquals(simong.restContext, folder2.id, null, [], () => { + // Create some content items and add them to the first folder + RestAPI.Content.createLink( + camAdminRestContext, + 'test', + 'test', + 'public', + 'http://www.google.com', + null, + [], + [], + (err, google) => { + assert.ok(!err); + RestAPI.Content.createLink( + camAdminRestContext, + 'marsupilamisausage', + 'marsupilamisausage', + 'public', + 'http://www.marsupilamisausage.com', + null, + [], + [], + (err, mars) => { + assert.ok(!err); + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + simong.restContext, + folder1.id, + [google.id, mars.id], + () => { + // Searching through the first folder should give the + // first 2 links. The other folder should still be empty + FoldersTestUtil.assertFolderSearchEquals( + simong.restContext, + folder1.id, + null, + [google, mars], + () => { + FoldersTestUtil.assertFolderSearchEquals(simong.restContext, folder2.id, null, [], () => { + // Assert that folders can be searched through FoldersTestUtil.assertFolderSearchEquals( simong.restContext, - folder2.id, - null, - [], + folder1.id, + 'marsupilamisausage', + [mars], () => { - // Assert that folders can be searched through - FoldersTestUtil.assertFolderSearchEquals( - simong.restContext, - folder1.id, - 'marsupilamisausage', - [mars], - () => { - return callback(); - } - ); + return callback(); } ); - } - ); - } - ); - } - ); - } - ); - } - ); + }); + } + ); + } + ); + } + ); + } + ); + }); }); }); }); @@ -334,66 +311,51 @@ describe('Folders', () => { privateTenant1 ) => { // Anonymous users cannot search this folder - FoldersTestUtil.assertFolderSearchFails( - publicTenant1.anonymousRestContext, - folder.id, - 401, - () => { - // A user from another tenant cannot search this folder - FoldersTestUtil.assertFolderSearchFails( - publicTenant2.publicUser.restContext, + FoldersTestUtil.assertFolderSearchFails(publicTenant1.anonymousRestContext, folder.id, 401, () => { + // A user from another tenant cannot search this folder + FoldersTestUtil.assertFolderSearchFails(publicTenant2.publicUser.restContext, folder.id, 401, () => { + // A user from the same tenant sees both public and loggedin content + FoldersTestUtil.assertFolderSearchEquals( + publicTenant1.publicUser.restContext, folder.id, - 401, + null, + [publicContent, loggedinContent], () => { - // A user from the same tenant sees both public and loggedin content + // A tenant admin sees everything FoldersTestUtil.assertFolderSearchEquals( - publicTenant1.publicUser.restContext, + publicTenant1.adminRestContext, folder.id, null, - [publicContent, loggedinContent], + [publicContent, loggedinContent, privateContent], () => { - // A tenant admin sees everything - FoldersTestUtil.assertFolderSearchEquals( - publicTenant1.adminRestContext, - folder.id, - null, - [publicContent, loggedinContent, privateContent], - () => { - // A tenant admin from another tenant cannot search in this folder - FoldersTestUtil.assertFolderSearchFails( - publicTenant2.adminRestContext, - folder.id, - 401, - () => { - // A manager sees everything - FoldersTestUtil.assertFolderSearchEquals( - simong.restContext, - folder.id, - null, - [publicContent, loggedinContent, privateContent], - () => { - // A global admin sees everything - FoldersTestUtil.assertFolderSearchEquals( - globalAdminRestContext, - folder.id, - null, - [publicContent, loggedinContent, privateContent], - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); + // A tenant admin from another tenant cannot search in this folder + FoldersTestUtil.assertFolderSearchFails(publicTenant2.adminRestContext, folder.id, 401, () => { + // A manager sees everything + FoldersTestUtil.assertFolderSearchEquals( + simong.restContext, + folder.id, + null, + [publicContent, loggedinContent, privateContent], + () => { + // A global admin sees everything + FoldersTestUtil.assertFolderSearchEquals( + globalAdminRestContext, + folder.id, + null, + [publicContent, loggedinContent, privateContent], + () => { + return callback(); + } + ); + } + ); + }); } ); } ); - } - ); + }); + }); } ); }); @@ -416,65 +378,45 @@ describe('Folders', () => { privateTenant1 ) => { // Anonymous users cannot search this folder - FoldersTestUtil.assertFolderSearchFails( - publicTenant1.anonymousRestContext, - folder.id, - 401, - () => { - // A user from another tenant cannot search this folder - FoldersTestUtil.assertFolderSearchFails( - publicTenant2.publicUser.restContext, - folder.id, - 401, - () => { - // A user from the same tenant cannot search this folder - FoldersTestUtil.assertFolderSearchFails( - publicTenant1.publicUser.restContext, - folder.id, - 401, - () => { - // A tenant admin sees everything + FoldersTestUtil.assertFolderSearchFails(publicTenant1.anonymousRestContext, folder.id, 401, () => { + // A user from another tenant cannot search this folder + FoldersTestUtil.assertFolderSearchFails(publicTenant2.publicUser.restContext, folder.id, 401, () => { + // A user from the same tenant cannot search this folder + FoldersTestUtil.assertFolderSearchFails(publicTenant1.publicUser.restContext, folder.id, 401, () => { + // A tenant admin sees everything + FoldersTestUtil.assertFolderSearchEquals( + publicTenant1.adminRestContext, + folder.id, + null, + [publicContent, loggedinContent, privateContent], + () => { + // A tenant admin from another tenant cannot search in this folder + FoldersTestUtil.assertFolderSearchFails(publicTenant2.adminRestContext, folder.id, 401, () => { + // A manager sees everything FoldersTestUtil.assertFolderSearchEquals( - publicTenant1.adminRestContext, + simong.restContext, folder.id, null, [publicContent, loggedinContent, privateContent], () => { - // A tenant admin from another tenant cannot search in this folder - FoldersTestUtil.assertFolderSearchFails( - publicTenant2.adminRestContext, + // A global admin sees everything + FoldersTestUtil.assertFolderSearchEquals( + globalAdminRestContext, folder.id, - 401, + null, + [publicContent, loggedinContent, privateContent], () => { - // A manager sees everything - FoldersTestUtil.assertFolderSearchEquals( - simong.restContext, - folder.id, - null, - [publicContent, loggedinContent, privateContent], - () => { - // A global admin sees everything - FoldersTestUtil.assertFolderSearchEquals( - globalAdminRestContext, - folder.id, - null, - [publicContent, loggedinContent, privateContent], - () => { - return callback(); - } - ); - } - ); + return callback(); } ); } ); - } - ); - } - ); - } - ); + }); + } + ); + }); + }); + }); } ); }); @@ -489,46 +431,24 @@ describe('Folders', () => { assert.ok(!err); // Setup a folder - RestAPI.Folders.createFolder( - simong.restContext, - 'aaaaaa', - null, - null, - null, - null, - (err, folderA) => { + RestAPI.Folders.createFolder(simong.restContext, 'aaaaaa', null, null, null, null, (err, folderA) => { + assert.ok(!err); + RestAPI.Folders.createFolder(simong.restContext, 'bbbbbb', null, null, null, null, (err, folderB) => { assert.ok(!err); - RestAPI.Folders.createFolder( - simong.restContext, - 'bbbbbb', - null, - null, - null, - null, - (err, folderB) => { - assert.ok(!err); - // Search for it - FoldersTestUtil.assertGeneralFolderSearchEquals( - simong.restContext, - null, - [folderA, folderB], - [], - () => { - // Search on the display name - FoldersTestUtil.assertGeneralFolderSearchEquals( - simong.restContext, - folderA.displayName, - [folderA], - [folderB], - callback - ); - } - ); - } - ); - } - ); + // Search for it + FoldersTestUtil.assertGeneralFolderSearchEquals(simong.restContext, null, [folderA, folderB], [], () => { + // Search on the display name + FoldersTestUtil.assertGeneralFolderSearchEquals( + simong.restContext, + folderA.displayName, + [folderA], + [folderB], + callback + ); + }); + }); + }); }); }); @@ -559,32 +479,17 @@ describe('Folders', () => { FoldersAPI.emitter.emit(FoldersConstants.events.UPDATED_FOLDER_PREVIEWS, folder); // When we search for the folder it should contain our thumbnail - FoldersTestUtil.assertGeneralFolderSearchEquals( - simong.restContext, - null, - [folder], - [], - () => { - // When we remove the thumbnail, it should be removed from the search result - FoldersDAO.setPreviews(folder, {}, (err, folder) => { - assert.ok(!err); + FoldersTestUtil.assertGeneralFolderSearchEquals(simong.restContext, null, [folder], [], () => { + // When we remove the thumbnail, it should be removed from the search result + FoldersDAO.setPreviews(folder, {}, (err, folder) => { + assert.ok(!err); - FoldersAPI.emitter.emit( - FoldersConstants.events.UPDATED_FOLDER_PREVIEWS, - folder - ); + FoldersAPI.emitter.emit(FoldersConstants.events.UPDATED_FOLDER_PREVIEWS, folder); - // When we search for the folder it should contain our thumbnail - FoldersTestUtil.assertGeneralFolderSearchEquals( - simong.restContext, - null, - [folder], - [], - callback - ); - }); - } - ); + // When we search for the folder it should contain our thumbnail + FoldersTestUtil.assertGeneralFolderSearchEquals(simong.restContext, null, [folder], [], callback); + }); + }); } ); } @@ -726,39 +631,33 @@ describe('Folders', () => { [folder1, folder2], () => { // Create a message on the first folder - FoldersTestUtil.assertCreateMessageSucceeds( - simong.restContext, - folder1.id, - searchTerm, - null, - message => { - // Verify that the first folder returns in the search result but not the second folder - FoldersTestUtil.assertGeneralFolderSearchEquals( - simong.restContext, - searchTerm, - [folder1], - [folder2], - () => { - // Delete the message - FoldersTestUtil.assertDeleteMessageSucceeds( - simong.restContext, - folder1.id, - message.created, - message => { - // Verify that none of the folders return in the search results - FoldersTestUtil.assertGeneralFolderSearchEquals( - simong.restContext, - searchTerm, - [], - [folder1, folder2], - callback - ); - } - ); - } - ); - } - ); + FoldersTestUtil.assertCreateMessageSucceeds(simong.restContext, folder1.id, searchTerm, null, message => { + // Verify that the first folder returns in the search result but not the second folder + FoldersTestUtil.assertGeneralFolderSearchEquals( + simong.restContext, + searchTerm, + [folder1], + [folder2], + () => { + // Delete the message + FoldersTestUtil.assertDeleteMessageSucceeds( + simong.restContext, + folder1.id, + message.created, + message => { + // Verify that none of the folders return in the search results + FoldersTestUtil.assertGeneralFolderSearchEquals( + simong.restContext, + searchTerm, + [], + [folder1, folder2], + callback + ); + } + ); + } + ); + }); } ); }); @@ -771,30 +670,18 @@ describe('Folders', () => { * Test that verifies only valid principal ids return results */ it('verify the principal id gets validated', callback => { - SearchTestsUtil.searchAll( - camAdminRestContext, - 'folder-library', - [''], - null, - (err, results) => { + SearchTestsUtil.searchAll(camAdminRestContext, 'folder-library', [''], null, (err, results) => { + assert.strictEqual(err.code, 400); + assert.ok(!results); + + SearchTestsUtil.searchAll(camAdminRestContext, 'folder-library', ['invalid-user-id'], null, (err, results) => { assert.strictEqual(err.code, 400); assert.ok(!results); - SearchTestsUtil.searchAll( - camAdminRestContext, - 'folder-library', - ['invalid-user-id'], - null, - (err, results) => { - assert.strictEqual(err.code, 400); - assert.ok(!results); - - return callback(); - } - ); - } - ); - }); + return callback(); + }); + }); + }); /** * Test that verifies that the visibility of folders is taken into account when searching for folders @@ -807,98 +694,83 @@ describe('Folders', () => { assert.ok(!err); // Setup a public, loggedin and private folder - FoldersTestUtil.generateTestFoldersWithVisibility( - simong.restContext, - 1, - 'public', - publicFolder => { - FoldersTestUtil.generateTestFoldersWithVisibility( - simong.restContext, - 1, - 'loggedin', - loggedinFolder => { - FoldersTestUtil.generateTestFoldersWithVisibility( - simong.restContext, - 1, - 'private', - privateFolder => { - // Anonymous users can only see the public folder - FoldersTestUtil.assertFolderLibrarySearch( - camAnonymousRestContext, - simong.user.id, - 'disp', - [publicFolder], - [loggedinFolder, privateFolder], - () => { - // Anonymous users from other tenants can only see the public folder - FoldersTestUtil.assertFolderLibrarySearch( - gtAnonymousRestContext, - simong.user.id, - 'disp', - [publicFolder], - [loggedinFolder, privateFolder], - () => { - // Users from other tenants can only see the public folder - FoldersTestUtil.assertFolderLibrarySearch( - stuartf.restContext, - simong.user.id, - 'disp', - [publicFolder], - [loggedinFolder, privateFolder], - () => { - // Authenticated users can only see the public and logged in folders - FoldersTestUtil.assertFolderLibrarySearch( - nico.restContext, - simong.user.id, - 'disp', - [publicFolder, loggedinFolder], - [privateFolder], - () => { - // Simong can see all folders as he created them. Keep in mind that - // we need to search for some term as the endpoint would otherwise - // only return implicit results (and not filtered by access) - FoldersTestUtil.assertFolderLibrarySearch( - simong.restContext, - simong.user.id, - 'disp', - [publicFolder, loggedinFolder, privateFolder], - [], - () => { - // Tenant administrators can see everything - FoldersTestUtil.assertFolderLibrarySearch( - camAdminRestContext, - simong.user.id, - 'disp', - [publicFolder, loggedinFolder, privateFolder], - [], - () => { - // Tenant administrators from other tenants can only see the public folder - FoldersTestUtil.assertFolderLibrarySearch( - gtAdminRestContext, - simong.user.id, - 'disp', - [publicFolder], - [loggedinFolder, privateFolder], - callback - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + FoldersTestUtil.generateTestFoldersWithVisibility(simong.restContext, 1, 'public', publicFolder => { + FoldersTestUtil.generateTestFoldersWithVisibility(simong.restContext, 1, 'loggedin', loggedinFolder => { + FoldersTestUtil.generateTestFoldersWithVisibility(simong.restContext, 1, 'private', privateFolder => { + // Anonymous users can only see the public folder + FoldersTestUtil.assertFolderLibrarySearch( + camAnonymousRestContext, + simong.user.id, + 'disp', + [publicFolder], + [loggedinFolder, privateFolder], + () => { + // Anonymous users from other tenants can only see the public folder + FoldersTestUtil.assertFolderLibrarySearch( + gtAnonymousRestContext, + simong.user.id, + 'disp', + [publicFolder], + [loggedinFolder, privateFolder], + () => { + // Users from other tenants can only see the public folder + FoldersTestUtil.assertFolderLibrarySearch( + stuartf.restContext, + simong.user.id, + 'disp', + [publicFolder], + [loggedinFolder, privateFolder], + () => { + // Authenticated users can only see the public and logged in folders + FoldersTestUtil.assertFolderLibrarySearch( + nico.restContext, + simong.user.id, + 'disp', + [publicFolder, loggedinFolder], + [privateFolder], + () => { + // Simong can see all folders as he created them. Keep in mind that + // we need to search for some term as the endpoint would otherwise + // only return implicit results (and not filtered by access) + FoldersTestUtil.assertFolderLibrarySearch( + simong.restContext, + simong.user.id, + 'disp', + [publicFolder, loggedinFolder, privateFolder], + [], + () => { + // Tenant administrators can see everything + FoldersTestUtil.assertFolderLibrarySearch( + camAdminRestContext, + simong.user.id, + 'disp', + [publicFolder, loggedinFolder, privateFolder], + [], + () => { + // Tenant administrators from other tenants can only see the public folder + FoldersTestUtil.assertFolderLibrarySearch( + gtAdminRestContext, + simong.user.id, + 'disp', + [publicFolder], + [loggedinFolder, privateFolder], + callback + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); + }); + }); }); }); }); @@ -913,93 +785,59 @@ describe('Folders', () => { assert.ok(!err); // Setup a public folder with some content - FoldersTestUtil.generateTestFoldersWithVisibility( - simong.restContext, - 1, - 'public', - folder => { - RestAPI.Content.createLink( - simong.restContext, - 'public', - 'public', - 'public', - 'http://www.google.com', - null, - [], - [], - (err, link) => { - assert.ok(!err); + FoldersTestUtil.generateTestFoldersWithVisibility(simong.restContext, 1, 'public', folder => { + RestAPI.Content.createLink( + simong.restContext, + 'public', + 'public', + 'public', + 'http://www.google.com', + null, + [], + [], + (err, link) => { + assert.ok(!err); - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - simong.restContext, - folder.id, - [link.id], - () => { - // Sanity-check that folder can be found in a general search - FoldersTestUtil.assertGeneralFolderSearchEquals( - simong.restContext, - 'disp', - [folder], - [], - () => { - // Sanity-check we can search in the folder - FoldersTestUtil.assertFolderSearchEquals( - simong.restContext, - folder.id, - null, - [link], - () => { - // Delete all the things - SearchTestsUtil.deleteAll(() => { - // Check that we can no longer find the folder - FoldersTestUtil.assertGeneralFolderSearchEquals( - simong.restContext, - 'disp', - [], - [folder], - () => { - // Sanity check that searching in the folder returns 0 results - FoldersTestUtil.assertFolderSearchEquals( - simong.restContext, - folder.id, - null, - [], - () => { - // Reindex all the things - SearchTestsUtil.reindexAll(globalAdminRestContext, () => { - // Check that we can now find the folder again - FoldersTestUtil.assertGeneralFolderSearchEquals( - simong.restContext, - 'disp', - [folder], - [], - () => { - // Check that we can search in the folder again - FoldersTestUtil.assertFolderSearchEquals( - simong.restContext, - folder.id, - null, - [link], - callback - ); - } - ); - }); - } - ); - } - ); - }); - } - ); - } - ); - } - ); - } - ); - } - ); + FoldersTestUtil.assertAddContentItemsToFolderSucceeds(simong.restContext, folder.id, [link.id], () => { + // Sanity-check that folder can be found in a general search + FoldersTestUtil.assertGeneralFolderSearchEquals(simong.restContext, 'disp', [folder], [], () => { + // Sanity-check we can search in the folder + FoldersTestUtil.assertFolderSearchEquals(simong.restContext, folder.id, null, [link], () => { + // Delete all the things + SearchTestsUtil.deleteAll(() => { + // Check that we can no longer find the folder + FoldersTestUtil.assertGeneralFolderSearchEquals(simong.restContext, 'disp', [], [folder], () => { + // Sanity check that searching in the folder returns 0 results + FoldersTestUtil.assertFolderSearchEquals(simong.restContext, folder.id, null, [], () => { + // Reindex all the things + SearchTestsUtil.reindexAll(globalAdminRestContext, () => { + // Check that we can now find the folder again + FoldersTestUtil.assertGeneralFolderSearchEquals( + simong.restContext, + 'disp', + [folder], + [], + () => { + // Check that we can search in the folder again + FoldersTestUtil.assertFolderSearchEquals( + simong.restContext, + folder.id, + null, + [link], + callback + ); + } + ); + }); + }); + }); + }); + }); + }); + }); + } + ); + }); }); }); @@ -1011,41 +849,25 @@ describe('Folders', () => { assert.ok(!err); // Setup a public folder - FoldersTestUtil.generateTestFoldersWithVisibility( - simong.restContext, - 1, - 'public', - folder => { - // Sanity-check that it can be found - FoldersTestUtil.assertGeneralFolderSearchEquals( - simong.restContext, - 'disp', - [folder], - [], - () => { - // Update the folder's name and visibility - const updates = { displayName: 'New displayName', visibility: 'private' }; - RestAPI.Folders.updateFolder( - simong.restContext, - folder.id, - updates, - (err, updatedFolder) => { - assert.ok(!err); + FoldersTestUtil.generateTestFoldersWithVisibility(simong.restContext, 1, 'public', folder => { + // Sanity-check that it can be found + FoldersTestUtil.assertGeneralFolderSearchEquals(simong.restContext, 'disp', [folder], [], () => { + // Update the folder's name and visibility + const updates = { displayName: 'New displayName', visibility: 'private' }; + RestAPI.Folders.updateFolder(simong.restContext, folder.id, updates, (err, updatedFolder) => { + assert.ok(!err); - // Assert that the folder's metadata has changed - FoldersTestUtil.assertGeneralFolderSearchEquals( - simong.restContext, - 'display', - [updatedFolder], - [], - callback - ); - } - ); - } - ); - } - ); + // Assert that the folder's metadata has changed + FoldersTestUtil.assertGeneralFolderSearchEquals( + simong.restContext, + 'display', + [updatedFolder], + [], + callback + ); + }); + }); + }); }); }); @@ -1057,79 +879,64 @@ describe('Folders', () => { assert.ok(!err); // Setup a public folder - FoldersTestUtil.generateTestFoldersWithVisibility( - simong.restContext, - 1, - 'public', - folder => { - // Add some public content - RestAPI.Content.createLink( - simong.restContext, - 'displayName', - 'description', - 'public', - 'http://www.google.com', - null, - [], - [], - (err, link) => { - assert.ok(!err); + FoldersTestUtil.generateTestFoldersWithVisibility(simong.restContext, 1, 'public', folder => { + // Add some public content + RestAPI.Content.createLink( + simong.restContext, + 'displayName', + 'description', + 'public', + 'http://www.google.com', + null, + [], + [], + (err, link) => { + assert.ok(!err); - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - simong.restContext, - folder.id, - [link.id], - () => { - // Sanity-check that the content item can be found by Nico - SearchTestsUtil.searchAll( - nico.restContext, - 'general', - null, - { resourceTypes: 'content', q: 'displayName' }, - (err, results) => { - assert.ok(!err); - assert.ok(_.findWhere(results.results, { id: link.id })); + FoldersTestUtil.assertAddContentItemsToFolderSucceeds(simong.restContext, folder.id, [link.id], () => { + // Sanity-check that the content item can be found by Nico + SearchTestsUtil.searchAll( + nico.restContext, + 'general', + null, + { resourceTypes: 'content', q: 'displayName' }, + (err, results) => { + assert.ok(!err); + assert.ok(_.findWhere(results.results, { id: link.id })); - // Make the folder and all content in it private - const updates = { visibility: 'private' }; - RestAPI.Folders.updateFolder( - simong.restContext, - folder.id, - updates, - (err, data) => { - assert.ok(!err); - RestAPI.Folders.updateFolderContentVisibility( - simong.restContext, - folder.id, - 'private', - (err, data) => { - assert.ok(!err); + // Make the folder and all content in it private + const updates = { visibility: 'private' }; + RestAPI.Folders.updateFolder(simong.restContext, folder.id, updates, (err, data) => { + assert.ok(!err); + RestAPI.Folders.updateFolderContentVisibility( + simong.restContext, + folder.id, + 'private', + (err, data) => { + assert.ok(!err); - // Nico should no longer be able to see the content item - SearchTestsUtil.searchAll( - nico.restContext, - 'general', - null, - { resourceTypes: 'content', q: 'displayName' }, - (err, results) => { - assert.ok(!err); - assert.ok(!_.findWhere(results.results, { id: link.id })); + // Nico should no longer be able to see the content item + SearchTestsUtil.searchAll( + nico.restContext, + 'general', + null, + { resourceTypes: 'content', q: 'displayName' }, + (err, results) => { + assert.ok(!err); + assert.ok(!_.findWhere(results.results, { id: link.id })); - return callback(); - } - ); - } - ); - } - ); - } - ); + return callback(); + } + ); + } + ); + }); } ); - } - ); - } - ); + }); + } + ); + }); }); }); @@ -1141,93 +948,88 @@ describe('Folders', () => { assert.ok(!err); // Setup a private folder - FoldersTestUtil.generateTestFoldersWithVisibility( - simong.restContext, - 1, - 'private', - folder => { - // Sanity-check that the folder cannot be found by Nico - SearchTestsUtil.searchAll( - nico.restContext, - 'general', - null, - { resourceTypes: 'folder', q: 'displayName' }, - (err, results) => { - assert.ok(!err); - assert.ok(!_.findWhere(results.results, { id: folder.id })); + FoldersTestUtil.generateTestFoldersWithVisibility(simong.restContext, 1, 'private', folder => { + // Sanity-check that the folder cannot be found by Nico + SearchTestsUtil.searchAll( + nico.restContext, + 'general', + null, + { resourceTypes: 'folder', q: 'displayName' }, + (err, results) => { + assert.ok(!err); + assert.ok(!_.findWhere(results.results, { id: folder.id })); - // Make Nico a viewer - const memberUpdate = {}; - memberUpdate[nico.user.id] = 'viewer'; - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - simong.restContext, - simong.restContext, - folder.id, - memberUpdate, - () => { - // Nico should now be able to search for the folder - SearchTestsUtil.searchAll( - nico.restContext, - 'general', - null, - { resourceTypes: 'folder', q: 'displayName' }, - (err, results) => { - assert.ok(!err); - assert.ok(_.findWhere(results.results, { id: folder.id })); + // Make Nico a viewer + const memberUpdate = {}; + memberUpdate[nico.user.id] = 'viewer'; + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + simong.restContext, + simong.restContext, + folder.id, + memberUpdate, + () => { + // Nico should now be able to search for the folder + SearchTestsUtil.searchAll( + nico.restContext, + 'general', + null, + { resourceTypes: 'folder', q: 'displayName' }, + (err, results) => { + assert.ok(!err); + assert.ok(_.findWhere(results.results, { id: folder.id })); - // Make Nico a manager - memberUpdate[nico.user.id] = 'manager'; - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - simong.restContext, - simong.restContext, - folder.id, - memberUpdate, - () => { - // Nico should still be able to search for the folder - SearchTestsUtil.searchAll( - nico.restContext, - 'general', - null, - { resourceTypes: 'folder', q: 'displayName' }, - (err, results) => { - assert.ok(!err); - assert.ok(_.findWhere(results.results, { id: folder.id })); + // Make Nico a manager + memberUpdate[nico.user.id] = 'manager'; + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + simong.restContext, + simong.restContext, + folder.id, + memberUpdate, + () => { + // Nico should still be able to search for the folder + SearchTestsUtil.searchAll( + nico.restContext, + 'general', + null, + { resourceTypes: 'folder', q: 'displayName' }, + (err, results) => { + assert.ok(!err); + assert.ok(_.findWhere(results.results, { id: folder.id })); - // Removing Nico's membership - memberUpdate[nico.user.id] = false; - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - simong.restContext, - simong.restContext, - folder.id, - memberUpdate, - () => { - // Nico should no longer see the folder in the search results - SearchTestsUtil.searchAll( - nico.restContext, - 'general', - null, - { resourceTypes: 'folder', q: 'displayName' }, - (err, results) => { - assert.ok(!err); - assert.ok(!_.findWhere(results.results, { id: folder.id })); + // Removing Nico's membership + memberUpdate[nico.user.id] = false; + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + simong.restContext, + simong.restContext, + folder.id, + memberUpdate, + () => { + // Nico should no longer see the folder in the search results + SearchTestsUtil.searchAll( + nico.restContext, + 'general', + null, + { resourceTypes: 'folder', q: 'displayName' }, + (err, results) => { + assert.ok(!err); + assert.ok(!_.findWhere(results.results, { id: folder.id })); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); }); }); @@ -1239,101 +1041,89 @@ describe('Folders', () => { assert.ok(!err); // Setup a private folder - FoldersTestUtil.generateTestFoldersWithVisibility( - simong.restContext, - 1, - 'private', - folder => { - // Create a private content item - const uniqueString = TestsUtil.generateRandomText(5); - RestAPI.Content.createLink( - simong.restContext, - uniqueString, - 'description', - 'private', - 'http://www.google.com', - null, - [], - [], - (err, link) => { - assert.ok(!err); + FoldersTestUtil.generateTestFoldersWithVisibility(simong.restContext, 1, 'private', folder => { + // Create a private content item + const uniqueString = TestsUtil.generateRandomText(5); + RestAPI.Content.createLink( + simong.restContext, + uniqueString, + 'description', + 'private', + 'http://www.google.com', + null, + [], + [], + (err, link) => { + assert.ok(!err); - // Add the content item to the folder - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - simong.restContext, - folder.id, - [link.id], - () => { - // Sanity-check that the content item cannot be found by Nico - SearchTestsUtil.searchAll( - nico.restContext, - 'general', - null, - { resourceTypes: 'content', q: uniqueString }, - (err, results) => { - assert.ok(!err); - assert.ok(!_.findWhere(results.results, { id: link.id })); + // Add the content item to the folder + FoldersTestUtil.assertAddContentItemsToFolderSucceeds(simong.restContext, folder.id, [link.id], () => { + // Sanity-check that the content item cannot be found by Nico + SearchTestsUtil.searchAll( + nico.restContext, + 'general', + null, + { resourceTypes: 'content', q: uniqueString }, + (err, results) => { + assert.ok(!err); + assert.ok(!_.findWhere(results.results, { id: link.id })); - // Make Nico a viewer - const memberUpdate = {}; - memberUpdate[nico.user.id] = 'viewer'; - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - simong.restContext, - simong.restContext, - folder.id, - memberUpdate, - () => { - // Nico should now be able to search for the content item - SearchTestsUtil.searchAll( - nico.restContext, - 'general', - null, - { q: uniqueString, scope: '_network' }, - (err, results) => { - assert.ok(!err); - assert.ok(_.findWhere(results.results, { id: link.id })); + // Make Nico a viewer + const memberUpdate = {}; + memberUpdate[nico.user.id] = 'viewer'; + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + simong.restContext, + simong.restContext, + folder.id, + memberUpdate, + () => { + // Nico should now be able to search for the content item + SearchTestsUtil.searchAll( + nico.restContext, + 'general', + null, + { q: uniqueString, scope: '_network' }, + (err, results) => { + assert.ok(!err); + assert.ok(_.findWhere(results.results, { id: link.id })); - // Make Nico a manager - memberUpdate[nico.user.id] = 'manager'; - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - simong.restContext, - simong.restContext, - folder.id, - memberUpdate, - () => { - // Nico should still be able to search for the content item - SearchTestsUtil.searchAll( - nico.restContext, - 'general', - null, - { resourceTypes: 'content', q: uniqueString }, - (err, results) => { - assert.ok(!err); - assert.ok(_.findWhere(results.results, { id: link.id })); + // Make Nico a manager + memberUpdate[nico.user.id] = 'manager'; + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + simong.restContext, + simong.restContext, + folder.id, + memberUpdate, + () => { + // Nico should still be able to search for the content item + SearchTestsUtil.searchAll( + nico.restContext, + 'general', + null, + { resourceTypes: 'content', q: uniqueString }, + (err, results) => { + assert.ok(!err); + assert.ok(_.findWhere(results.results, { id: link.id })); - // Removing Nico's membership - memberUpdate[nico.user.id] = false; - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - simong.restContext, - simong.restContext, - folder.id, - memberUpdate, - () => { - // Nico should no longer see the content item in the search results - SearchTestsUtil.searchAll( - nico.restContext, - 'general', - null, - { resourceTypes: 'content', q: uniqueString }, - (err, results) => { - assert.ok(!err); - assert.ok( - !_.findWhere(results.results, { id: link.id }) - ); + // Removing Nico's membership + memberUpdate[nico.user.id] = false; + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + simong.restContext, + simong.restContext, + folder.id, + memberUpdate, + () => { + // Nico should no longer see the content item in the search results + SearchTestsUtil.searchAll( + nico.restContext, + 'general', + null, + { resourceTypes: 'content', q: uniqueString }, + (err, results) => { + assert.ok(!err); + assert.ok(!_.findWhere(results.results, { id: link.id })); - return callback(); - } - ); + return callback(); } ); } @@ -1348,10 +1138,10 @@ describe('Folders', () => { ); } ); - } - ); - } - ); + }); + } + ); + }); }); }); @@ -1363,50 +1153,22 @@ describe('Folders', () => { assert.ok(!err); // Setup a public folder - FoldersTestUtil.generateTestFoldersWithVisibility( - simong.restContext, - 1, - 'public', - folder => { - // Sanity-check that it can be found - FoldersTestUtil.assertGeneralFolderSearchEquals( - simong.restContext, - 'disp', - [folder], - [], - () => { - // Delete the folder - FoldersTestUtil.assertDeleteFolderSucceeds( - simong.restContext, - folder.id, - false, - () => { - // Assert that the folder's metadata cannot be found - FoldersTestUtil.assertGeneralFolderSearchEquals( - simong.restContext, - 'disp', - [], - [folder], - () => { - // Reindex everything - SearchTestsUtil.reindexAll(globalAdminRestContext, () => { - // We still should not be able to find the folder - FoldersTestUtil.assertGeneralFolderSearchEquals( - simong.restContext, - 'disp', - [], - [folder], - callback - ); - }); - } - ); - } - ); - } - ); - } - ); + FoldersTestUtil.generateTestFoldersWithVisibility(simong.restContext, 1, 'public', folder => { + // Sanity-check that it can be found + FoldersTestUtil.assertGeneralFolderSearchEquals(simong.restContext, 'disp', [folder], [], () => { + // Delete the folder + FoldersTestUtil.assertDeleteFolderSucceeds(simong.restContext, folder.id, false, () => { + // Assert that the folder's metadata cannot be found + FoldersTestUtil.assertGeneralFolderSearchEquals(simong.restContext, 'disp', [], [folder], () => { + // Reindex everything + SearchTestsUtil.reindexAll(globalAdminRestContext, () => { + // We still should not be able to find the folder + FoldersTestUtil.assertGeneralFolderSearchEquals(simong.restContext, 'disp', [], [folder], callback); + }); + }); + }); + }); + }); }); }); @@ -1418,86 +1180,81 @@ describe('Folders', () => { assert.ok(!err); // Setup a folder - FoldersTestUtil.generateTestFoldersWithVisibility( - simong.restContext, - 1, - 'private', - folder => { - // Add some private content to the folder. We add it in two different ways - // to ensure that both mechanismes get properly indexed - RestAPI.Content.createLink( - simong.restContext, - 'private', - 'private', - 'private', - 'http://www.google.com', - null, - [], - [folder.id], - (err, link1) => { - assert.ok(!err); + FoldersTestUtil.generateTestFoldersWithVisibility(simong.restContext, 1, 'private', folder => { + // Add some private content to the folder. We add it in two different ways + // to ensure that both mechanismes get properly indexed + RestAPI.Content.createLink( + simong.restContext, + 'private', + 'private', + 'private', + 'http://www.google.com', + null, + [], + [folder.id], + (err, link1) => { + assert.ok(!err); - RestAPI.Content.createLink( - simong.restContext, - 'private', - 'private', - 'private', - 'http://www.google.com', - null, - [], - [], - (err, link2) => { - assert.ok(!err); - FoldersTestUtil.assertAddContentItemsToFolderSucceeds( - simong.restContext, - folder.id, - [link2.id], - () => { - // Assert that Nico can't find the items through search yet - SearchTestsUtil.searchAll( - nico.restContext, - 'general', - null, - { resourceTypes: 'content', q: 'private' }, - (err, results) => { - assert.ok(!err); - assert.ok(!_.findWhere(results.results, { id: link1.id })); - assert.ok(!_.findWhere(results.results, { id: link2.id })); + RestAPI.Content.createLink( + simong.restContext, + 'private', + 'private', + 'private', + 'http://www.google.com', + null, + [], + [], + (err, link2) => { + assert.ok(!err); + FoldersTestUtil.assertAddContentItemsToFolderSucceeds( + simong.restContext, + folder.id, + [link2.id], + () => { + // Assert that Nico can't find the items through search yet + SearchTestsUtil.searchAll( + nico.restContext, + 'general', + null, + { resourceTypes: 'content', q: 'private' }, + (err, results) => { + assert.ok(!err); + assert.ok(!_.findWhere(results.results, { id: link1.id })); + assert.ok(!_.findWhere(results.results, { id: link2.id })); - // Make Nico a member - const memberUpdate = {}; - memberUpdate[nico.user.id] = 'viewer'; - FoldersTestUtil.assertUpdateFolderMembersSucceeds( - simong.restContext, - simong.restContext, - folder.id, - memberUpdate, - () => { - // Now Nico should be able to see the items - SearchTestsUtil.searchAll( - nico.restContext, - 'general', - null, - { resourceTypes: 'content', q: 'private' }, - (err, results) => { - assert.ok(!err); - assert.ok(_.findWhere(results.results, { id: link1.id })); - assert.ok(_.findWhere(results.results, { id: link2.id })); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + // Make Nico a member + const memberUpdate = {}; + memberUpdate[nico.user.id] = 'viewer'; + FoldersTestUtil.assertUpdateFolderMembersSucceeds( + simong.restContext, + simong.restContext, + folder.id, + memberUpdate, + () => { + // Now Nico should be able to see the items + SearchTestsUtil.searchAll( + nico.restContext, + 'general', + null, + { resourceTypes: 'content', q: 'private' }, + (err, results) => { + assert.ok(!err); + assert.ok(_.findWhere(results.results, { id: link1.id })); + assert.ok(_.findWhere(results.results, { id: link2.id })); + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); }); }); }); diff --git a/packages/oae-following/lib/activity.js b/packages/oae-following/lib/activity.js index 545a129333..fcc6a1fc50 100644 --- a/packages/oae-following/lib/activity.js +++ b/packages/oae-following/lib/activity.js @@ -13,17 +13,17 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const ActivityAPI = require('oae-activity'); -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityModel = require('oae-activity/lib/model'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzUtil = require('oae-authz/lib/util'); +import * as ActivityAPI from 'oae-activity'; +import * as ActivityModel from 'oae-activity/lib/model'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as FollowingDAO from 'oae-following/lib/internal/dao'; +import * as FollowingAPI from 'oae-following'; -const FollowingAPI = require('oae-following'); -const { FollowingConstants } = require('oae-following/lib/constants'); -const FollowingDAO = require('oae-following/lib/internal/dao'); +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { ActivityConstants } from 'oae-activity/lib/constants'; +import { FollowingConstants } from 'oae-following/lib/constants'; /// /////////////////// // FOLLOWING-FOLLOW // @@ -85,32 +85,29 @@ FollowingAPI.emitter.on(FollowingConstants.events.FOLLOW, (ctx, followingUser, f /*! * Register a user association that presents all the followers of a user */ -ActivityAPI.registerActivityEntityAssociation( - 'user', - 'followers', - (associationsCtx, entity, callback) => { - // Get all the followers of the user - const userId = entity.user.id; - const userTenantAlias = entity.user.tenant.alias; - const userVisibility = entity.user.visibility; +ActivityAPI.registerActivityEntityAssociation('user', 'followers', (associationsCtx, entity, callback) => { + // Get all the followers of the user + const userId = entity.user.id; + const userTenantAlias = entity.user.tenant.alias; + const userVisibility = entity.user.visibility; - // When a user is private, their followers are effectively no longer associated - if (userVisibility === AuthzConstants.visibility.PRIVATE) { - return callback(null, []); + // When a user is private, their followers are effectively no longer associated + if (userVisibility === AuthzConstants.visibility.PRIVATE) { + return callback(null, []); + } + + FollowingDAO.getFollowers(userId, null, 10000, (err, followers) => { + if (err) { + return callback(err); } - FollowingDAO.getFollowers(userId, null, 10000, (err, followers) => { - if (err) { - return callback(err); - } - if (userVisibility === AuthzConstants.visibility.LOGGEDIN) { - // If the user is loggedin, only associate the user to followers that are within their tenant - followers = _.filter(followers, follower => { - return userTenantAlias === AuthzUtil.getPrincipalFromId(follower).tenantAlias; - }); - } + if (userVisibility === AuthzConstants.visibility.LOGGEDIN) { + // If the user is loggedin, only associate the user to followers that are within their tenant + followers = _.filter(followers, follower => { + return userTenantAlias === AuthzUtil.getPrincipalFromId(follower).tenantAlias; + }); + } - return callback(null, followers); - }); - } -); + return callback(null, followers); + }); +}); diff --git a/packages/oae-following/lib/api.js b/packages/oae-following/lib/api.js index d6b7b2b67e..db0419d542 100644 --- a/packages/oae-following/lib/api.js +++ b/packages/oae-following/lib/api.js @@ -13,18 +13,18 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const EmitterAPI = require('oae-emitter'); -const OaeUtil = require('oae-util/lib/util'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const PrincipalsUtil = require('oae-principals/lib/util'); -const { Validator } = require('oae-authz/lib/validator'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as EmitterAPI from 'oae-emitter'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; +import * as FollowingAuthz from 'oae-following/lib/authz'; -const FollowingAuthz = require('oae-following/lib/authz'); -const { FollowingConstants } = require('oae-following/lib/constants'); -const FollowingDAO = require('./internal/dao'); +import { Validator } from 'oae-authz/lib/validator'; +import { FollowingConstants } from 'oae-following/lib/constants'; +import * as FollowingDAO from './internal/dao'; /** * ### Events @@ -87,21 +87,13 @@ const getFollowers = function(ctx, userId, start, limit, callback) { } // Emit an event indicating that the followers for a user have been retrieved - FollowingAPI.emit( - FollowingConstants.events.GET_FOLLOWERS, - ctx, - userId, - start, - limit, - users, - err => { - if (err) { - return callback(err); - } - - return callback(null, users, nextToken); + FollowingAPI.emit(FollowingConstants.events.GET_FOLLOWERS, ctx, userId, start, limit, users, err => { + if (err) { + return callback(err); } - ); + + return callback(null, users, nextToken); + }); }); }); }); @@ -161,21 +153,13 @@ const getFollowing = function(ctx, userId, start, limit, callback) { } // Emit an event indicating that the followed users for a user have been retrieved - FollowingAPI.emit( - FollowingConstants.events.GET_FOLLOWING, - ctx, - userId, - start, - limit, - users, - err => { - if (err) { - return callback(err); - } - - return callback(null, users, nextToken); + FollowingAPI.emit(FollowingConstants.events.GET_FOLLOWING, ctx, userId, start, limit, users, err => { + if (err) { + return callback(err); } - ); + + return callback(null, users, nextToken); + }); }); }); }); @@ -193,9 +177,7 @@ const getFollowing = function(ctx, userId, start, limit, callback) { */ const follow = function(ctx, followedUserId, callback) { const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'You must be authenticated to follow a user' }) - .isLoggedInUser(ctx); + validator.check(null, { code: 401, msg: 'You must be authenticated to follow a user' }).isLoggedInUser(ctx); validator .check(followedUserId, { code: 400, @@ -222,6 +204,7 @@ const follow = function(ctx, followedUserId, callback) { if (err) { return callback(err); } + if (following[followedUserId]) { // The user is already following the target user, so we don't // have to do anything @@ -234,13 +217,7 @@ const follow = function(ctx, followedUserId, callback) { return callback(err); } - return FollowingAPI.emit( - FollowingConstants.events.FOLLOW, - ctx, - ctx.user(), - followedUser, - callback - ); + return FollowingAPI.emit(FollowingConstants.events.FOLLOW, ctx, ctx.user(), followedUser, callback); }); }); }); @@ -257,9 +234,7 @@ const follow = function(ctx, followedUserId, callback) { */ const unfollow = function(ctx, unfollowedUserId, callback) { const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'You must be authenticated to unfollow a user' }) - .isLoggedInUser(ctx); + validator.check(null, { code: 401, msg: 'You must be authenticated to unfollow a user' }).isLoggedInUser(ctx); validator .check(unfollowedUserId, { code: 400, @@ -276,13 +251,7 @@ const unfollow = function(ctx, unfollowedUserId, callback) { return callback(err); } - return FollowingAPI.emit( - FollowingConstants.events.UNFOLLOW, - ctx, - ctx.user(), - unfollowedUserId, - callback - ); + return FollowingAPI.emit(FollowingConstants.events.UNFOLLOW, ctx, ctx.user(), unfollowedUserId, callback); }); }; @@ -316,10 +285,4 @@ const _expandUserIds = function(ctx, userIds, callback) { }); }; -module.exports = { - emitter: FollowingAPI, - getFollowers, - getFollowing, - follow, - unfollow -}; +export { FollowingAPI as emitter, getFollowers, getFollowing, follow, unfollow }; diff --git a/packages/oae-following/lib/authz.js b/packages/oae-following/lib/authz.js index 959212a180..e4839955e3 100644 --- a/packages/oae-following/lib/authz.js +++ b/packages/oae-following/lib/authz.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const AuthzPermissions = require('oae-authz/lib/permissions'); +import * as AuthzPermissions from 'oae-authz/lib/permissions'; /** * Determine if the current user in context is allowed to see the followers list of the provided user. @@ -60,6 +60,7 @@ const canFollow = function(ctx, user, callback) { if (ctx.user() && ctx.user().id === user.id) { return callback({ code: 400, msg: 'A user cannot follow themselves' }); } + if (user.deleted) { return callback({ code: 404, msg: 'The user could not be found' }); } @@ -67,8 +68,4 @@ const canFollow = function(ctx, user, callback) { return AuthzPermissions.canInteract(ctx, user, callback); }; -module.exports = { - canFollow, - canViewFollowing, - canViewFollowers -}; +export { canFollow, canViewFollowing, canViewFollowers }; diff --git a/packages/oae-following/lib/constants.js b/packages/oae-following/lib/constants.js index cd252f7456..a3e3a6a6a4 100644 --- a/packages/oae-following/lib/constants.js +++ b/packages/oae-following/lib/constants.js @@ -31,4 +31,4 @@ FollowingConstants.search = { MAPPING_RESOURCE_FOLLOWING: 'resource_following' }; -module.exports = { FollowingConstants }; +export { FollowingConstants }; diff --git a/packages/oae-following/lib/init.js b/packages/oae-following/lib/init.js index 3f185e255b..5047870029 100644 --- a/packages/oae-following/lib/init.js +++ b/packages/oae-following/lib/init.js @@ -13,14 +13,14 @@ * permissions and limitations under the License. */ -const FollowingSearch = require('oae-following/lib/search'); +import * as FollowingSearch from 'oae-following/lib/search'; // Bind some plugins -// eslint-disable-next-line no-unused-vars -const activity = require('oae-following/lib/activity'); -// eslint-disable-next-line no-unused-vars -const principals = require('oae-following/lib/principals'); +// eslint-disable-next-line no-unused-vars, import/namespace +import * as activity from 'oae-following/lib/activity'; +// eslint-disable-next-line no-unused-vars, import/namespace +import * as principals from 'oae-following/lib/principals'; -module.exports = function(config, callback) { +export function init(config, callback) { return FollowingSearch.init(callback); -}; +} diff --git a/packages/oae-following/lib/internal/dao.js b/packages/oae-following/lib/internal/dao.js index a811da5d37..146255eff5 100644 --- a/packages/oae-following/lib/internal/dao.js +++ b/packages/oae-following/lib/internal/dao.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const Cassandra = require('oae-util/lib/cassandra'); +import * as Cassandra from 'oae-util/lib/cassandra'; /** * Get the list of followers of the specified user @@ -144,15 +144,13 @@ const saveFollows = function(followerUserId, followedUserIds, callback) { queries.push( // Query that indicates the follower is following the user { - query: - 'INSERT INTO "FollowingUsersFollowing" ("userId", "followingId", "value") VALUES (?, ?, ?)', + query: 'INSERT INTO "FollowingUsersFollowing" ("userId", "followingId", "value") VALUES (?, ?, ?)', parameters: [followerUserId, followedUserId, '1'] }, // Query that indicates the user is followed by the user { - query: - 'INSERT INTO "FollowingUsersFollowers" ("userId", "followerId", "value") VALUES (?, ?, ?)', + query: 'INSERT INTO "FollowingUsersFollowers" ("userId", "followerId", "value") VALUES (?, ?, ?)', parameters: [followedUserId, followerUserId, '1'] } ); @@ -198,10 +196,4 @@ const deleteFollows = function(followerUserId, followedUserIds, callback) { Cassandra.runBatchQuery(queries, callback); }; -module.exports = { - getFollowers, - getFollowing, - isFollowing, - saveFollows, - deleteFollows -}; +export { getFollowers, getFollowing, isFollowing, saveFollows, deleteFollows }; diff --git a/packages/oae-following/lib/migration.js b/packages/oae-following/lib/migration.js index f7ee261ac1..63c80c4f5d 100644 --- a/packages/oae-following/lib/migration.js +++ b/packages/oae-following/lib/migration.js @@ -1,4 +1,4 @@ -const Cassandra = require('oae-util/lib/cassandra'); +import { createColumnFamilies } from 'oae-util/lib/cassandra'; /** * Create the following database schema @@ -8,7 +8,7 @@ const Cassandra = require('oae-util/lib/cassandra'); * @api private */ const ensureSchema = function(callback) { - Cassandra.createColumnFamilies( + createColumnFamilies( { FollowingUsersFollowers: 'CREATE TABLE "FollowingUsersFollowers" ("userId" text, "followerId" text, "value" text, PRIMARY KEY ("userId", "followerId")) WITH COMPACT STORAGE', @@ -19,4 +19,4 @@ const ensureSchema = function(callback) { ); }; -module.exports = { ensureSchema }; +export { ensureSchema }; diff --git a/packages/oae-following/lib/principals.js b/packages/oae-following/lib/principals.js index 63d5699d32..58d73677e0 100644 --- a/packages/oae-following/lib/principals.js +++ b/packages/oae-following/lib/principals.js @@ -13,10 +13,9 @@ * permissions and limitations under the License. */ -const PrincipalsAPI = require('oae-principals'); - -const FollowingAuthz = require('oae-following/lib/authz'); -const FollowingDAO = require('oae-following/lib/internal/dao'); +import * as PrincipalsAPI from 'oae-principals'; +import * as FollowingAuthz from 'oae-following/lib/authz'; +import * as FollowingDAO from 'oae-following/lib/internal/dao'; /*! * Register a full user profile decorator that will indicate if the user in context @@ -26,6 +25,7 @@ PrincipalsAPI.registerFullUserProfileDecorator('following', (ctx, user, callback if (!ctx.user()) { return callback(); } + if (ctx.user().id === user.id) { return callback(); } @@ -48,6 +48,7 @@ PrincipalsAPI.registerFullUserProfileDecorator('following', (ctx, user, callback if (err && err.code !== 401) { return callback(err); } + if (err) { canFollow = false; } diff --git a/packages/oae-following/lib/rest.js b/packages/oae-following/lib/rest.js index 9f0e276649..9a18fed90c 100644 --- a/packages/oae-following/lib/rest.js +++ b/packages/oae-following/lib/rest.js @@ -13,10 +13,9 @@ * permissions and limitations under the License. */ -const OAE = require('oae-util/lib/oae'); -const OaeUtil = require('oae-util/lib/util'); - -const FollowingAPI = require('oae-following'); +import * as OAE from 'oae-util/lib/oae'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as FollowingAPI from 'oae-following'; /** * @REST getFollowingUserIdFollowers @@ -36,19 +35,13 @@ const FollowingAPI = require('oae-following'); */ OAE.tenantRouter.on('get', '/api/following/:userId/followers', (req, res) => { const limit = OaeUtil.getNumberParam(req.query.limit, 10, 1, 25); - FollowingAPI.getFollowers( - req.ctx, - req.params.userId, - req.query.start, - limit, - (err, followers, nextToken) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(200).send({ results: followers, nextToken }); + FollowingAPI.getFollowers(req.ctx, req.params.userId, req.query.start, limit, (err, followers, nextToken) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + return res.status(200).send({ results: followers, nextToken }); + }); }); /** @@ -69,19 +62,13 @@ OAE.tenantRouter.on('get', '/api/following/:userId/followers', (req, res) => { */ OAE.tenantRouter.on('get', '/api/following/:userId/following', (req, res) => { const limit = OaeUtil.getNumberParam(req.query.limit, 10, 1, 25); - FollowingAPI.getFollowing( - req.ctx, - req.params.userId, - req.query.start, - limit, - (err, following, nextToken) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(200).send({ results: following, nextToken }); + FollowingAPI.getFollowing(req.ctx, req.params.userId, req.query.start, limit, (err, following, nextToken) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + return res.status(200).send({ results: following, nextToken }); + }); }); /** diff --git a/packages/oae-following/lib/search.js b/packages/oae-following/lib/search.js index 3dae6a1d07..bf6f134699 100644 --- a/packages/oae-following/lib/search.js +++ b/packages/oae-following/lib/search.js @@ -13,13 +13,22 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const SearchAPI = require('oae-search'); -const SearchUtil = require('oae-search/lib/util'); +import _ from 'underscore'; -const FollowingAPI = require('oae-following'); -const { FollowingConstants } = require('oae-following/lib/constants'); -const FollowingDAO = require('oae-following/lib/internal/dao'); +import * as SearchAPI from 'oae-search'; +import * as SearchUtil from 'oae-search/lib/util'; +import * as FollowingAPI from 'oae-following'; +import * as FollowingDAO from 'oae-following/lib/internal/dao'; +import { FollowingConstants } from 'oae-following/lib/constants'; +import * as resourceFollowersSchema from './search/schema/resourceFollowersSchema'; +import * as resourceFollowingSchema from './search/schema/resourceFollowingSchema'; + +/// /////////////////// +// SEARCH ENDPOINTS // +/// /////////////////// + +import followers from './search/searches/followers'; +import following from './search/searches/following'; /** * Initializes the child search documents for the Following module @@ -30,7 +39,7 @@ const FollowingDAO = require('oae-following/lib/internal/dao'); const init = function(callback) { const followersChildSearchDocumentOptions = { resourceTypes: ['user'], - schema: require('./search/schema/resourceFollowersSchema'), + schema: resourceFollowersSchema, producer(resources, callback) { return _produceResourceFollowersDocuments(resources.slice(), callback); } @@ -38,7 +47,7 @@ const init = function(callback) { const followingChildSearchDocumentOptions = { resourceTypes: ['user'], - schema: require('./search/schema/resourceFollowingSchema'), + schema: resourceFollowingSchema, producer(resources, callback) { return _produceResourceFollowingDocuments(resources.slice(), callback); } @@ -83,11 +92,9 @@ const _produceResourceFollowersDocuments = function(resources, callback, _docume } _documents.push( - SearchUtil.createChildSearchDocument( - FollowingConstants.search.MAPPING_RESOURCE_FOLLOWERS, - resource.id, - { followers: followerUserIds } - ) + SearchUtil.createChildSearchDocument(FollowingConstants.search.MAPPING_RESOURCE_FOLLOWERS, resource.id, { + followers: followerUserIds + }) ); return _produceResourceFollowersDocuments(resources, callback, _documents, _errs); }); @@ -114,11 +121,9 @@ const _produceResourceFollowingDocuments = function(resources, callback, _docume } _documents.push( - SearchUtil.createChildSearchDocument( - FollowingConstants.search.MAPPING_RESOURCE_FOLLOWING, - resource.id, - { following: followingUserIds } - ) + SearchUtil.createChildSearchDocument(FollowingConstants.search.MAPPING_RESOURCE_FOLLOWING, resource.id, { + following: followingUserIds + }) ); return _produceResourceFollowingDocuments(resources, callback, _documents, _errs); }); @@ -151,17 +156,14 @@ const _getAllIds = function(method, id, start, limit, callback, _ids) { // There are no more ids to fetch, recursively get the next set return callback(null, _ids); } + // There are still more, recursively get the next set return _getAllIds(method, id, nextToken, limit, callback, _ids); }); }; -/// /////////////////// -// SEARCH ENDPOINTS // -/// /////////////////// - -SearchAPI.registerSearch('followers', require('./search/searches/followers')); -SearchAPI.registerSearch('following', require('./search/searches/following')); +SearchAPI.registerSearch('followers', followers); +SearchAPI.registerSearch('following', following); /// ///////////////// // INDEXING TASKS // @@ -177,12 +179,9 @@ FollowingAPI.emitter.on(FollowingConstants.events.FOLLOW, (ctx, followingUser, f /*! * Update the following search index and the followers search index based on the change in the following user and the unfollowed user */ -FollowingAPI.emitter.on( - FollowingConstants.events.UNFOLLOW, - (ctx, followingUser, unfollowedUserId) => { - return _handleIndexChange(ctx, followingUser.id, unfollowedUserId); - } -); +FollowingAPI.emitter.on(FollowingConstants.events.UNFOLLOW, (ctx, followingUser, unfollowedUserId) => { + return _handleIndexChange(ctx, followingUser.id, unfollowedUserId); +}); /*! * Handle the change in follower/following index. The `followingUserId` will have their following index updated @@ -208,6 +207,4 @@ const _handleIndexChange = function(ctx, followingUserId, followedUserId) { }); }; -module.exports = { - init -}; +export { init }; diff --git a/packages/oae-following/lib/search/schema/resourceFollowersSchema.js b/packages/oae-following/lib/search/schema/resourceFollowersSchema.js index 6033f6fcb5..dfcc0a5f1a 100644 --- a/packages/oae-following/lib/search/schema/resourceFollowersSchema.js +++ b/packages/oae-following/lib/search/schema/resourceFollowersSchema.js @@ -24,10 +24,8 @@ */ /* eslint-disable unicorn/filename-case */ -module.exports = { - followers: { - type: 'string', - store: 'no', - index: 'not_analyzed' - } +export const followers = { + type: 'string', + store: 'no', + index: 'not_analyzed' }; diff --git a/packages/oae-following/lib/search/schema/resourceFollowingSchema.js b/packages/oae-following/lib/search/schema/resourceFollowingSchema.js index 800a8cccf6..45812d3c11 100644 --- a/packages/oae-following/lib/search/schema/resourceFollowingSchema.js +++ b/packages/oae-following/lib/search/schema/resourceFollowingSchema.js @@ -23,10 +23,8 @@ * {String[]} schema.following A multi-value field that holds the resource ids of which the parent resource is following */ /* eslint-disable unicorn/filename-case */ -module.exports = { - following: { - type: 'string', - store: 'no', - index: 'not_analyzed' - } +export const following = { + type: 'string', + store: 'no', + index: 'not_analyzed' }; diff --git a/packages/oae-following/lib/search/searches/followers.js b/packages/oae-following/lib/search/searches/followers.js index 7a0708c229..bf1edb80df 100644 --- a/packages/oae-following/lib/search/searches/followers.js +++ b/packages/oae-following/lib/search/searches/followers.js @@ -13,13 +13,13 @@ * permissions and limitations under the License. */ -const OaeUtil = require('oae-util/lib/util'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const SearchUtil = require('oae-search/lib/util'); -const { Validator } = require('oae-authz/lib/validator'); +import * as OaeUtil from 'oae-util/lib/util'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as SearchUtil from 'oae-search/lib/util'; +import * as FollowingAuthz from 'oae-following/lib/authz'; -const FollowingAuthz = require('oae-following/lib/authz'); -const { FollowingConstants } = require('oae-following/lib/constants'); +import { Validator } from 'oae-authz/lib/validator'; +import { FollowingConstants } from 'oae-following/lib/constants'; /** * Search that searches a user's followers list. @@ -35,7 +35,7 @@ const { FollowingConstants } = require('oae-following/lib/constants'); * @param {Object} callback.err An error that occurred, if any * @param {SearchResult} callback.results An object that represents the results of the query */ -module.exports = function(ctx, opts, callback) { +export default function(ctx, opts, callback) { // Sanitize the search options opts = opts || {}; opts.pathParams = opts.pathParams || []; @@ -66,7 +66,9 @@ module.exports = function(ctx, opts, callback) { return _search(ctx, opts, callback); }); }); -}; +} + + /** * Perform the search that searches a user's followers list diff --git a/packages/oae-following/lib/search/searches/following.js b/packages/oae-following/lib/search/searches/following.js index a71c81fb25..ecb9c1a06a 100644 --- a/packages/oae-following/lib/search/searches/following.js +++ b/packages/oae-following/lib/search/searches/following.js @@ -13,13 +13,13 @@ * permissions and limitations under the License. */ -const OaeUtil = require('oae-util/lib/util'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const SearchUtil = require('oae-search/lib/util'); -const { Validator } = require('oae-authz/lib/validator'); +import * as OaeUtil from 'oae-util/lib/util'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as SearchUtil from 'oae-search/lib/util'; +import * as FollowingAuthz from 'oae-following/lib/authz'; -const FollowingAuthz = require('oae-following/lib/authz'); -const { FollowingConstants } = require('oae-following/lib/constants'); +import { Validator } from 'oae-authz/lib/validator'; +import { FollowingConstants } from 'oae-following/lib/constants'; /** * Search that searches through the list of user's that a user follows @@ -35,7 +35,7 @@ const { FollowingConstants } = require('oae-following/lib/constants'); * @param {Object} callback.err An error that occurred, if any * @param {SearchResult} callback.results An object that represents the results of the query */ -module.exports = function(ctx, opts, callback) { +export default function(ctx, opts, callback) { // Sanitize the search options opts = opts || {}; opts.pathParams = opts.pathParams || []; @@ -66,7 +66,9 @@ module.exports = function(ctx, opts, callback) { return _search(ctx, opts, callback); }); }); -}; +} + + /** * Perform the search that searches a user's list of followed users diff --git a/packages/oae-following/lib/test/util.js b/packages/oae-following/lib/test/util.js index aa50ce7c1f..f85af5ccf5 100644 --- a/packages/oae-following/lib/test/util.js +++ b/packages/oae-following/lib/test/util.js @@ -13,12 +13,12 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const RestAPI = require('oae-rest'); -const SearchTestUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests/lib/util'); +import * as RestAPI from 'oae-rest'; +import * as SearchTestUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests/lib/util'; /** * Create 2 users, one following the other @@ -121,34 +121,22 @@ const assertGetAllFollowersEquals = function(restCtx, userId, opts, expectedFoll * @param {User[]} callback.followers All followers of the specified user * @throws {AssertionError} Thrown if any assertions fail */ -const assertGetAllFollowersSucceeds = function( - restCtx, - userId, - opts, - callback, - _followers, - _nextToken -) { +const assertGetAllFollowersSucceeds = function(restCtx, userId, opts, callback, _followers, _nextToken) { if (_nextToken === null) { return callback(_followers); } opts = opts || {}; - assertGetFollowersSucceeds( - restCtx, - userId, - { start: _nextToken, limit: opts.batchSize }, - result => { - return assertGetAllFollowersSucceeds( - restCtx, - userId, - opts, - callback, - _.union(_followers, result.results), - result.nextToken - ); - } - ); + assertGetFollowersSucceeds(restCtx, userId, { start: _nextToken, limit: opts.batchSize }, result => { + return assertGetAllFollowersSucceeds( + restCtx, + userId, + opts, + callback, + _.union(_followers, result.results), + result.nextToken + ); + }); }; /** @@ -165,22 +153,17 @@ const assertGetAllFollowersSucceeds = function( */ const assertGetFollowersSucceeds = function(restCtx, userId, opts, callback) { opts = opts || {}; - RestAPI.Following.getFollowers( - restCtx, - userId, - opts.start, - opts.limit, - (err, result, nextToken) => { - assert.ok(!err); - assert.ok(_.isArray(result.results)); - if (_.isNumber(opts.limit) && opts.limit > 0) { - assert.ok(result.results.length <= opts.limit); - } - assert.ok(_.isString(result.nextToken) || _.isNull(result.nextToken)); - - return callback(result, nextToken); + RestAPI.Following.getFollowers(restCtx, userId, opts.start, opts.limit, (err, result, nextToken) => { + assert.ok(!err); + assert.ok(_.isArray(result.results)); + if (_.isNumber(opts.limit) && opts.limit > 0) { + assert.ok(result.results.length <= opts.limit); } - ); + + assert.ok(_.isString(result.nextToken) || _.isNull(result.nextToken)); + + return callback(result, nextToken); + }); }; /** @@ -196,13 +179,7 @@ const assertGetFollowersSucceeds = function(restCtx, userId, opts, callback) { * @param {User[]} callback.following All followed users of the specified user * @throws {AssertionError} Thrown if any assertions fail */ -const assertGetAllFollowingEquals = function( - restCtx, - userId, - opts, - expectedFollowingIds, - callback -) { +const assertGetAllFollowingEquals = function(restCtx, userId, opts, expectedFollowingIds, callback) { assertGetAllFollowingSucceeds(restCtx, userId, opts, following => { assert.deepStrictEqual(_.pluck(following, 'id').sort(), expectedFollowingIds.slice().sort()); return callback(following); @@ -220,34 +197,22 @@ const assertGetAllFollowingEquals = function( * @param {User[]} callback.following All followed users of the specified user * @throws {AssertionError} Thrown if any assertions fail */ -const assertGetAllFollowingSucceeds = function( - restCtx, - userId, - opts, - callback, - _following, - _nextToken -) { +const assertGetAllFollowingSucceeds = function(restCtx, userId, opts, callback, _following, _nextToken) { if (_nextToken === null) { return callback(_following); } opts = opts || {}; - assertGetFollowingSucceeds( - restCtx, - userId, - { start: _nextToken, limit: opts.batchSize }, - result => { - return assertGetAllFollowingSucceeds( - restCtx, - userId, - opts, - callback, - _.union(_following, result.results), - result.nextToken - ); - } - ); + assertGetFollowingSucceeds(restCtx, userId, { start: _nextToken, limit: opts.batchSize }, result => { + return assertGetAllFollowingSucceeds( + restCtx, + userId, + opts, + callback, + _.union(_following, result.results), + result.nextToken + ); + }); }; /** @@ -264,22 +229,17 @@ const assertGetAllFollowingSucceeds = function( */ const assertGetFollowingSucceeds = function(restCtx, userId, opts, callback) { opts = opts || {}; - RestAPI.Following.getFollowing( - restCtx, - userId, - opts.start, - opts.limit, - (err, result, nextToken) => { - assert.ok(!err); - assert.ok(_.isArray(result.results)); - if (_.isNumber(opts.limit) && opts.limit > 0) { - assert.ok(result.results.length <= opts.limit); - } - assert.ok(_.isString(result.nextToken) || _.isNull(result.nextToken)); - - return callback(result, nextToken); + RestAPI.Following.getFollowing(restCtx, userId, opts.start, opts.limit, (err, result, nextToken) => { + assert.ok(!err); + assert.ok(_.isArray(result.results)); + if (_.isNumber(opts.limit) && opts.limit > 0) { + assert.ok(result.results.length <= opts.limit); } - ); + + assert.ok(_.isString(result.nextToken) || _.isNull(result.nextToken)); + + return callback(result, nextToken); + }); }; /** @@ -291,26 +251,14 @@ const assertGetFollowingSucceeds = function(restCtx, userId, opts, callback) { * @param {RestContext} followerRestCtx The REST context that can be used to execute requests on behalf of the followed user * @param {Function} callback Standard callback function */ -const assertFollows = function( - followerUserId, - followerRestCtx, - followedUserId, - followedRestCtx, - callback -) { - _findFollowerAndFollowing( - followerUserId, - followerRestCtx, - followedUserId, - followedRestCtx, - (follower, followed) => { - assert.ok(follower); - assert.strictEqual(follower.id, followerUserId); - assert.ok(followed); - assert.strictEqual(followed.id, followedUserId); - return callback(); - } - ); +const assertFollows = function(followerUserId, followerRestCtx, followedUserId, followedRestCtx, callback) { + _findFollowerAndFollowing(followerUserId, followerRestCtx, followedUserId, followedRestCtx, (follower, followed) => { + assert.ok(follower); + assert.strictEqual(follower.id, followerUserId); + assert.ok(followed); + assert.strictEqual(followed.id, followedUserId); + return callback(); + }); }; /** @@ -322,30 +270,18 @@ const assertFollows = function( * @param {RestContext} followerRestCtx The REST context that can be used to execute requests on behalf of the followed user * @param {Function} callback Standard callback function */ -const assertDoesNotFollow = function( - followerUserId, - followerRestCtx, - followedUserId, - followedRestCtx, - callback -) { - _findFollowerAndFollowing( - followerUserId, - followerRestCtx, - followedUserId, - followedRestCtx, - (follower, followed) => { - if (follower) { - assert.notStrictEqual(follower.id, followerUserId); - } - - if (followed) { - assert.notStrictEqual(followed.id, followedUserId); - } - - return callback(); +const assertDoesNotFollow = function(followerUserId, followerRestCtx, followedUserId, followedRestCtx, callback) { + _findFollowerAndFollowing(followerUserId, followerRestCtx, followedUserId, followedRestCtx, (follower, followed) => { + if (follower) { + assert.notStrictEqual(follower.id, followerUserId); } - ); + + if (followed) { + assert.notStrictEqual(followed.id, followedUserId); + } + + return callback(); + }); }; /** @@ -461,30 +397,18 @@ const searchFollowerAndFollowing = function( followedRestCtx, callback ) { - SearchTestUtil.searchAll( - followerRestCtx, - 'following', - [followerUserId], - null, - (err, followingResponse) => { + SearchTestUtil.searchAll(followerRestCtx, 'following', [followerUserId], null, (err, followingResponse) => { + assert.ok(!err); + + SearchTestUtil.searchAll(followedRestCtx, 'followers', [followedUserId], null, (err, followerResponse) => { assert.ok(!err); - SearchTestUtil.searchAll( - followedRestCtx, - 'followers', - [followedUserId], - null, - (err, followerResponse) => { - assert.ok(!err); - - return callback( - _.findWhere(followerResponse.results, { id: followerUserId }), - _.findWhere(followingResponse.results, { id: followedUserId }) - ); - } + return callback( + _.findWhere(followerResponse.results, { id: followerUserId }), + _.findWhere(followingResponse.results, { id: followedUserId }) ); - } - ); + }); + }); }; /** @@ -499,13 +423,7 @@ const searchFollowerAndFollowing = function( * @param {User} [callback.followed] The followed user from the followers list. If unspecified, the user was not found * @api private */ -const _findFollowerAndFollowing = function( - followerUserId, - followerRestCtx, - followedUserId, - followedRestCtx, - callback -) { +const _findFollowerAndFollowing = function(followerUserId, followerRestCtx, followedUserId, followedRestCtx, callback) { // To ensure the first item would be the user we're looking for, we simply slice one character off the end as the start assertGetAllFollowingSucceeds(followerRestCtx, followerUserId, null, following => { const followed = _.findWhere(following, { id: followedUserId }); @@ -519,7 +437,7 @@ const _findFollowerAndFollowing = function( }); }; -module.exports = { +export { createFollowerAndFollowed, followByAll, followAll, diff --git a/packages/oae-following/tests/test-activity.js b/packages/oae-following/tests/test-activity.js index f1b5484c19..796de516fb 100644 --- a/packages/oae-following/tests/test-activity.js +++ b/packages/oae-following/tests/test-activity.js @@ -13,22 +13,20 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); - -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityTestsUtil = require('oae-activity/lib/test/util'); -const { ContentConstants } = require('oae-content/lib/constants'); -const { DiscussionsConstants } = require('oae-discussions/lib/constants'); -const EmailTestsUtil = require('oae-email/lib/test/util'); -const { PrincipalsConstants } = require('oae-principals/lib/constants'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const RestUtil = require('oae-rest/lib/util'); -const TestsUtil = require('oae-tests/lib/util'); - -const { FollowingConstants } = require('oae-following/lib/constants'); -const FollowingTestsUtil = require('oae-following/lib/test/util'); +import assert from 'assert'; +import _ from 'underscore'; + +import { ActivityConstants } from 'oae-activity/lib/constants'; +import { PrincipalsConstants } from 'oae-principals/lib/constants'; +import { ContentConstants } from 'oae-content/lib/constants'; +import { DiscussionsConstants } from 'oae-discussions/lib/constants'; +import { FollowingConstants } from 'oae-following/lib/constants'; + +import * as ActivityTestsUtil from 'oae-activity/lib/test/util'; +import * as EmailTestsUtil from 'oae-email/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests/lib/util'; +import * as FollowingTestsUtil from 'oae-following/lib/test/util'; describe('Following Activity', () => { let camAdminRestContext = null; @@ -44,17 +42,17 @@ describe('Following Activity', () => { }); /*! - * Clear any pending/collected emails so they don't impact the following tests - */ + * Clear any pending/collected emails so they don't impact the following tests + */ beforeEach(EmailTestsUtil.clearEmailCollections); /*! - * Verify the contents of the follow activity - * - * @param {Activity} followActivity The activity whose content to verify - * @param {String} actorUserId The id of the user that should be the actor - * @param {String} objectUserId The id of the user that should be the object - */ + * Verify the contents of the follow activity + * + * @param {Activity} followActivity The activity whose content to verify + * @param {String} actorUserId The id of the user that should be the actor + * @param {String} objectUserId The id of the user that should be the object + */ const _assertFollowActivity = function(followActivity, actorUserId, objectUserId) { ActivityTestsUtil.assertActivity( followActivity, @@ -72,27 +70,17 @@ describe('Following Activity', () => { // Create 2 users, one following the other FollowingTestsUtil.createFollowerAndFollowed(camAdminRestContext, (follower, followed) => { // Get the follower's activity stream and ensure the activity is there - ActivityTestsUtil.collectAndGetActivityStream( - follower.restContext, - follower.user.id, - null, - (err, response) => { + ActivityTestsUtil.collectAndGetActivityStream(follower.restContext, follower.user.id, null, (err, response) => { + assert.ok(!err); + _assertFollowActivity(response.items[0], follower.user.id, followed.user.id); + + // Get the followed user's activity stream and ensure the activity is there + ActivityTestsUtil.collectAndGetActivityStream(followed.restContext, followed.user.id, null, (err, response) => { assert.ok(!err); _assertFollowActivity(response.items[0], follower.user.id, followed.user.id); - - // Get the followed user's activity stream and ensure the activity is there - ActivityTestsUtil.collectAndGetActivityStream( - followed.restContext, - followed.user.id, - null, - (err, response) => { - assert.ok(!err); - _assertFollowActivity(response.items[0], follower.user.id, followed.user.id); - return callback(); - } - ); - } - ); + return callback(); + }); + }); }); }); @@ -349,160 +337,143 @@ describe('Following Activity', () => { // Followed user adds nico to the first group let updateMembers = {}; updateMembers[nico.user.id] = 'member'; - RestAPI.Group.setGroupMembers( - followed.restContext, - group0.id, - updateMembers, - err => { - assert.ok(!err); - - // Ensure the following user **does not** get this activity. To do this, we ensure the latest activity is still the discussion they created earlier - ActivityTestsUtil.collectAndGetActivityStream( - follower.restContext, - follower.user.id, - null, - (err, response) => { + RestAPI.Group.setGroupMembers(followed.restContext, group0.id, updateMembers, err => { + assert.ok(!err); + + // Ensure the following user **does not** get this activity. To do this, we ensure the latest activity is still the discussion they created earlier + ActivityTestsUtil.collectAndGetActivityStream( + follower.restContext, + follower.user.id, + null, + (err, response) => { + assert.ok(!err); + ActivityTestsUtil.assertActivity( + response.items[0], + DiscussionsConstants.activity.ACTIVITY_DISCUSSION_CREATE, + ActivityConstants.verbs.CREATE, + followed.user.id, + discussion0.id + ); + + // Nico adds the followed user to the second group + updateMembers = {}; + updateMembers[followed.user.id] = 'member'; + RestAPI.Group.setGroupMembers(nico.restContext, group1.id, updateMembers, err => { assert.ok(!err); - ActivityTestsUtil.assertActivity( - response.items[0], - DiscussionsConstants.activity.ACTIVITY_DISCUSSION_CREATE, - ActivityConstants.verbs.CREATE, - followed.user.id, - discussion0.id - ); - // Nico adds the followed user to the second group - updateMembers = {}; - updateMembers[followed.user.id] = 'member'; - RestAPI.Group.setGroupMembers( - nico.restContext, - group1.id, - updateMembers, - err => { + // Ensure the following user got this activity + ActivityTestsUtil.collectAndGetActivityStream( + follower.restContext, + follower.user.id, + null, + (err, response) => { assert.ok(!err); + ActivityTestsUtil.assertActivity( + response.items[0], + PrincipalsConstants.activity.ACTIVITY_GROUP_ADD_MEMBER, + ActivityConstants.verbs.ADD, + nico.user.id, + followed.user.id, + group1.id + ); - // Ensure the following user got this activity - ActivityTestsUtil.collectAndGetActivityStream( - follower.restContext, - follower.user.id, - null, - (err, response) => { + // Followed user shares the first link with nico + RestAPI.Content.shareContent( + followed.restContext, + link0.id, + [nico.user.id], + err => { assert.ok(!err); - ActivityTestsUtil.assertActivity( - response.items[0], - PrincipalsConstants.activity - .ACTIVITY_GROUP_ADD_MEMBER, - ActivityConstants.verbs.ADD, - nico.user.id, - followed.user.id, - group1.id - ); - // Followed user shares the first link with nico - RestAPI.Content.shareContent( - followed.restContext, - link0.id, - [nico.user.id], - err => { + // Ensure the follower **does not** get this activity in their feed. To do this, we ensure the latest activity is still the add group member activity from before + ActivityTestsUtil.collectAndGetActivityStream( + follower.restContext, + follower.user.id, + null, + (err, response) => { assert.ok(!err); + ActivityTestsUtil.assertActivity( + response.items[0], + PrincipalsConstants.activity.ACTIVITY_GROUP_ADD_MEMBER, + ActivityConstants.verbs.ADD, + nico.user.id, + followed.user.id, + group1.id + ); - // Ensure the follower **does not** get this activity in their feed. To do this, we ensure the latest activity is still the add group member activity from before - ActivityTestsUtil.collectAndGetActivityStream( - follower.restContext, - follower.user.id, - null, - (err, response) => { + // Nico shares the second link with the followed user + RestAPI.Content.shareContent( + nico.restContext, + link1.id, + [followed.user.id], + err => { assert.ok(!err); - ActivityTestsUtil.assertActivity( - response.items[0], - PrincipalsConstants.activity - .ACTIVITY_GROUP_ADD_MEMBER, - ActivityConstants.verbs.ADD, - nico.user.id, - followed.user.id, - group1.id - ); - // Nico shares the second link with the followed user - RestAPI.Content.shareContent( - nico.restContext, - link1.id, - [followed.user.id], - err => { + // Ensure the follower gets this activity in their feed + ActivityTestsUtil.collectAndGetActivityStream( + follower.restContext, + follower.user.id, + null, + (err, response) => { assert.ok(!err); + ActivityTestsUtil.assertActivity( + response.items[0], + ContentConstants.activity.ACTIVITY_CONTENT_SHARE, + ActivityConstants.verbs.SHARE, + nico.user.id, + link1.id, + followed.user.id + ); - // Ensure the follower gets this activity in their feed - ActivityTestsUtil.collectAndGetActivityStream( - follower.restContext, - follower.user.id, - null, - (err, response) => { + // Followed user shares the first discussion with nico + RestAPI.Discussions.shareDiscussion( + followed.restContext, + discussion0.id, + [nico.user.id], + err => { assert.ok(!err); - ActivityTestsUtil.assertActivity( - response.items[0], - ContentConstants.activity - .ACTIVITY_CONTENT_SHARE, - ActivityConstants.verbs.SHARE, - nico.user.id, - link1.id, - followed.user.id - ); - // Followed user shares the first discussion with nico - RestAPI.Discussions.shareDiscussion( - followed.restContext, - discussion0.id, - [nico.user.id], - err => { + // Ensure the follower **does not** get this activity in their feed. To do this, we ensure the latest activity is still the content share activity from before + ActivityTestsUtil.collectAndGetActivityStream( + follower.restContext, + follower.user.id, + null, + (err, response) => { assert.ok(!err); + ActivityTestsUtil.assertActivity( + response.items[0], + ContentConstants.activity.ACTIVITY_CONTENT_SHARE, + ActivityConstants.verbs.SHARE, + nico.user.id, + link1.id, + followed.user.id + ); - // Ensure the follower **does not** get this activity in their feed. To do this, we ensure the latest activity is still the content share activity from before - ActivityTestsUtil.collectAndGetActivityStream( - follower.restContext, - follower.user.id, - null, - (err, response) => { + // Nico shares the second discussion with the followed user + RestAPI.Discussions.shareDiscussion( + nico.restContext, + discussion1.id, + [followed.user.id], + err => { assert.ok(!err); - ActivityTestsUtil.assertActivity( - response.items[0], - ContentConstants.activity - .ACTIVITY_CONTENT_SHARE, - ActivityConstants.verbs - .SHARE, - nico.user.id, - link1.id, - followed.user.id - ); - // Nico shares the second discussion with the followed user - RestAPI.Discussions.shareDiscussion( - nico.restContext, - discussion1.id, - [followed.user.id], - err => { + // Ensure the follower gets this activity in their feed + ActivityTestsUtil.collectAndGetActivityStream( + follower.restContext, + follower.user.id, + null, + (err, response) => { assert.ok(!err); - - // Ensure the follower gets this activity in their feed - ActivityTestsUtil.collectAndGetActivityStream( - follower.restContext, - follower.user.id, - null, - (err, response) => { - assert.ok(!err); - ActivityTestsUtil.assertActivity( - response.items[0], - DiscussionsConstants - .activity - .ACTIVITY_DISCUSSION_SHARE, - ActivityConstants - .verbs.SHARE, - nico.user.id, - discussion1.id, - followed.user.id - ); - return callback(); - } + ActivityTestsUtil.assertActivity( + response.items[0], + DiscussionsConstants.activity + .ACTIVITY_DISCUSSION_SHARE, + ActivityConstants.verbs.SHARE, + nico.user.id, + discussion1.id, + followed.user.id ); + return callback(); } ); } @@ -521,10 +492,10 @@ describe('Following Activity', () => { ); } ); - } - ); - } - ); + }); + } + ); + }); } ); } @@ -558,50 +529,40 @@ describe('Following Activity', () => { assert.ok(!err); // Afterward, Cam user sets their visibility to loggedin - RestAPI.User.updateUser( - camUser.restContext, - camUser.user.id, - { visibility: 'loggedin' }, - err => { - assert.ok(!err); + RestAPI.User.updateUser(camUser.restContext, camUser.user.id, { visibility: 'loggedin' }, err => { + assert.ok(!err); - // Then, Cam user creates a content item, which will create an activity that would have been sent to GT user if their privacy wasn't - // changed to loggedin - RestAPI.Content.createLink( - camUser.restContext, - 'Google', - 'Google', - 'public', - 'http://www.google.ca', - [], - [], - [], - (err, link) => { - assert.ok(!err); + // Then, Cam user creates a content item, which will create an activity that would have been sent to GT user if their privacy wasn't + // changed to loggedin + RestAPI.Content.createLink( + camUser.restContext, + 'Google', + 'Google', + 'public', + 'http://www.google.ca', + [], + [], + [], + (err, link) => { + assert.ok(!err); - // Verify that GT user did not receive the content creation activity - ActivityTestsUtil.collectAndGetActivityStream( - gtUser.restContext, - null, - null, - (err, response) => { - assert.ok(!err); + // Verify that GT user did not receive the content creation activity + ActivityTestsUtil.collectAndGetActivityStream(gtUser.restContext, null, null, (err, response) => { + assert.ok(!err); - // Ensure the GT user did not get the activity by ensuring their latest activity is the follow activity they performed - ActivityTestsUtil.assertActivity( - response.items[0], - FollowingConstants.activity.ACTIVITY_FOLLOW, - ActivityConstants.verbs.FOLLOW, - gtUser.user.id, - camUser.user.id - ); - return callback(); - } + // Ensure the GT user did not get the activity by ensuring their latest activity is the follow activity they performed + ActivityTestsUtil.assertActivity( + response.items[0], + FollowingConstants.activity.ACTIVITY_FOLLOW, + ActivityConstants.verbs.FOLLOW, + gtUser.user.id, + camUser.user.id ); - } - ); - } - ); + return callback(); + }); + } + ); + }); }); }); }); @@ -613,50 +574,40 @@ describe('Following Activity', () => { it('verify followers from external tenants no longer receive activity for users who become private', callback => { FollowingTestsUtil.createFollowerAndFollowed(camAdminRestContext, (follower, followed) => { // Afterward, camUser sets their visibility to loggedin - RestAPI.User.updateUser( - followed.restContext, - followed.user.id, - { visibility: 'private' }, - err => { - assert.ok(!err); + RestAPI.User.updateUser(followed.restContext, followed.user.id, { visibility: 'private' }, err => { + assert.ok(!err); - // Then, followed user creates a content item, which will create an activity that would have been sent to the follower if their - // privacy wasn't changed to private - RestAPI.Content.createLink( - followed.restContext, - 'Google', - 'Google', - 'public', - 'http://www.google.ca', - [], - [], - [], - (err, link) => { - assert.ok(!err); + // Then, followed user creates a content item, which will create an activity that would have been sent to the follower if their + // privacy wasn't changed to private + RestAPI.Content.createLink( + followed.restContext, + 'Google', + 'Google', + 'public', + 'http://www.google.ca', + [], + [], + [], + (err, link) => { + assert.ok(!err); - // Verify that follower user did not receive the content creation activity - ActivityTestsUtil.collectAndGetActivityStream( - follower.restContext, - null, - null, - (err, response) => { - assert.ok(!err); + // Verify that follower user did not receive the content creation activity + ActivityTestsUtil.collectAndGetActivityStream(follower.restContext, null, null, (err, response) => { + assert.ok(!err); - // Ensure the gtUser did not get the activity by ensuring their latest activity is the follow activity they performed - ActivityTestsUtil.assertActivity( - response.items[0], - FollowingConstants.activity.ACTIVITY_FOLLOW, - ActivityConstants.verbs.FOLLOW, - follower.user.id, - followed.user.id - ); - return callback(); - } + // Ensure the gtUser did not get the activity by ensuring their latest activity is the follow activity they performed + ActivityTestsUtil.assertActivity( + response.items[0], + FollowingConstants.activity.ACTIVITY_FOLLOW, + ActivityConstants.verbs.FOLLOW, + follower.user.id, + followed.user.id ); - } - ); - } - ); + return callback(); + }); + } + ); + }); }); }); @@ -680,60 +631,52 @@ describe('Following Activity', () => { assert.strictEqual(messages[0].to[0].address, followed.user.email); assert.notStrictEqual(messages[0].html.indexOf(follower.user.profilePath), -1); - ActivityTestsUtil.collectAndGetNotificationStream( - followed.restContext, - null, - (err, response) => { - assert.ok(!err); + ActivityTestsUtil.collectAndGetNotificationStream(followed.restContext, null, (err, response) => { + assert.ok(!err); - // Only the follow activity should be in the stream - assert.strictEqual(response.items.length, 1); - ActivityTestsUtil.assertActivity( - response.items[0], - 'following-follow', - 'follow', - follower.user.id, - followed.user.id - ); + // Only the follow activity should be in the stream + assert.strictEqual(response.items.length, 1); + ActivityTestsUtil.assertActivity( + response.items[0], + 'following-follow', + 'follow', + follower.user.id, + followed.user.id + ); - // Ensure the unread notification count was set to 1 - RestAPI.User.getMe(followed.restContext, (err, me) => { + // Ensure the unread notification count was set to 1 + RestAPI.User.getMe(followed.restContext, (err, me) => { + assert.ok(!err); + assert.strictEqual(me.notificationsUnread, 1); + + // Another user follows our user + RestAPI.Following.follow(otherFollower.restContext, followed.user.id, err => { assert.ok(!err); - assert.strictEqual(me.notificationsUnread, 1); - // Another user follows our user - RestAPI.Following.follow(otherFollower.restContext, followed.user.id, err => { + // Get the notifications for the followed user + ActivityTestsUtil.collectAndGetNotificationStream(followed.restContext, null, (err, response) => { assert.ok(!err); - // Get the notifications for the followed user - ActivityTestsUtil.collectAndGetNotificationStream( - followed.restContext, - null, - (err, response) => { - assert.ok(!err); - - // There should be only 1 (aggregated) following-follow activity in the notification stream - assert.strictEqual(response.items.length, 1); - ActivityTestsUtil.assertActivity( - response.items[0], - 'following-follow', - 'follow', - [follower.user.id, otherFollower.user.id], - followed.user.id - ); - - // Ensure the unread notification count is still 1 - RestAPI.User.getMe(followed.restContext, (err, me) => { - assert.ok(!err); - assert.strictEqual(me.notificationsUnread, 1); - return callback(); - }); - } + // There should be only 1 (aggregated) following-follow activity in the notification stream + assert.strictEqual(response.items.length, 1); + ActivityTestsUtil.assertActivity( + response.items[0], + 'following-follow', + 'follow', + [follower.user.id, otherFollower.user.id], + followed.user.id ); + + // Ensure the unread notification count is still 1 + RestAPI.User.getMe(followed.restContext, (err, me) => { + assert.ok(!err); + assert.strictEqual(me.notificationsUnread, 1); + return callback(); + }); }); }); - } - ); + }); + }); }); }); }); @@ -743,52 +686,48 @@ describe('Following Activity', () => { * Test that verifies that users get a single aggregated activity in their email when multiple users follow them */ it('verify users get a single aggregated activity in their email when 1 or multiple users follow them', callback => { - TestsUtil.generateTestUsers( - camAdminRestContext, - 4, - (err, users, simon, branden, nico, bert) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 4, (err, users, simon, branden, nico, bert) => { + assert.ok(!err); - // Both Branden and Nico will follow Simon - RestAPI.Following.follow(branden.restContext, simon.user.id, err => { + // Both Branden and Nico will follow Simon + RestAPI.Following.follow(branden.restContext, simon.user.id, err => { + assert.ok(!err); + RestAPI.Following.follow(nico.restContext, simon.user.id, err => { assert.ok(!err); - RestAPI.Following.follow(nico.restContext, simon.user.id, err => { - assert.ok(!err); - // Fetch the emails and ensure Simon got a single email with a single activity - EmailTestsUtil.collectAndFetchAllEmails(messages => { - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].to[0].address, simon.user.email); - assert.strictEqual(messages[0].html.match(/data-activity-id/g).length, 1); + // Fetch the emails and ensure Simon got a single email with a single activity + EmailTestsUtil.collectAndFetchAllEmails(messages => { + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].to[0].address, simon.user.email); + assert.strictEqual(messages[0].html.match(/data-activity-id/g).length, 1); - // Assert both Branden and Nico are mentioned in the email - assert.ok(messages[0].html.indexOf(branden.user.profilePath) > 0); - assert.ok(messages[0].html.indexOf(nico.user.profilePath) > 0); + // Assert both Branden and Nico are mentioned in the email + assert.ok(messages[0].html.indexOf(branden.user.profilePath) > 0); + assert.ok(messages[0].html.indexOf(nico.user.profilePath) > 0); - // Sanity check that with a single user a single activity is received - RestAPI.Following.follow(bert.restContext, simon.user.id, err => { - assert.ok(!err); + // Sanity check that with a single user a single activity is received + RestAPI.Following.follow(bert.restContext, simon.user.id, err => { + assert.ok(!err); - // Fetch the emails and ensure Simon got a single email with a single activity - EmailTestsUtil.collectAndFetchAllEmails(messages => { - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].to[0].address, simon.user.email); - assert.strictEqual(messages[0].html.match(/data-activity-id/g).length, 1); + // Fetch the emails and ensure Simon got a single email with a single activity + EmailTestsUtil.collectAndFetchAllEmails(messages => { + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].to[0].address, simon.user.email); + assert.strictEqual(messages[0].html.match(/data-activity-id/g).length, 1); - // Assert both Branden and Nico are not mentioned in the email - assert.strictEqual(messages[0].html.indexOf(branden.user.profilePath), -1); - assert.strictEqual(messages[0].html.indexOf(nico.user.profilePath), -1); + // Assert both Branden and Nico are not mentioned in the email + assert.strictEqual(messages[0].html.indexOf(branden.user.profilePath), -1); + assert.strictEqual(messages[0].html.indexOf(nico.user.profilePath), -1); - // Assert Bert is mentioned in the email - assert.ok(messages[0].html.indexOf(bert.user.profilePath) > 0); - return callback(); - }); + // Assert Bert is mentioned in the email + assert.ok(messages[0].html.indexOf(bert.user.profilePath) > 0); + return callback(); }); }); }); }); - } - ); + }); + }); }); /** @@ -804,97 +743,87 @@ describe('Following Activity', () => { const bert = _.values(users)[2]; /*! - * Following has 2 "groupBy" specifications that can aggregate activities. One grouping by actor and one grouping by object. This - * results in the following groups eventually being created for the following test: - * - * Group #1: Simon followed * - * Group #2: * followed Branden - * Group #3: * followed Bert - */ + * Following has 2 "groupBy" specifications that can aggregate activities. One grouping by actor and one grouping by object. This + * results in the following groups eventually being created for the following test: + * + * Group #1: Simon followed * + * Group #2: * followed Branden + * Group #3: * followed Bert + */ /*! - * Simon follows Branden. This activity becomes 2 aggregates, one for Group #1 and one for Group #2, but since the activities are - * identical, only one activity is delivered that is referenced by both: - * - * Aggregate State: - * - * Group #1: Simon followed (Branden) [references Activity #1] - * Group #2: (Simon) followed Branden [references Activity #1] - * - * Activity Feed State: - * - * Activity #1: Simon followed Branden - */ + * Simon follows Branden. This activity becomes 2 aggregates, one for Group #1 and one for Group #2, but since the activities are + * identical, only one activity is delivered that is referenced by both: + * + * Aggregate State: + * + * Group #1: Simon followed (Branden) [references Activity #1] + * Group #2: (Simon) followed Branden [references Activity #1] + * + * Activity Feed State: + * + * Activity #1: Simon followed Branden + */ RestAPI.Following.follow(simon.restContext, branden.user.id, err => { assert.ok(!err); - ActivityTestsUtil.collectAndGetActivityStream( - simon.restContext, - simon.user.id, - null, - (err, response) => { + ActivityTestsUtil.collectAndGetActivityStream(simon.restContext, simon.user.id, null, (err, response) => { + assert.ok(!err); + + /*! + * Simon follows Bert. This activity joins the Group #1 aggregate, and "orphans" the Group #2 aggregate (i.e., Group #2 + * aggregate no longer references any activity since that activity gets deleted and replaced with an updated one). The new + * states should be: + * + * Aggregate State: + * + * Group #1: Simon followed (Branden,Bert) [references Activity #2] + * Group #2: (Simon) followed Branden [references Activity #1 (deleted)] + * Group #3: (Simon) followed Bert [(no reference since Group #3 "claimed" the activity)] + * + * Activity Feed State: + * + * Activity #2: Simon followed Branden and Bert + */ + RestAPI.Following.follow(simon.restContext, bert.user.id, err => { assert.ok(!err); - /*! - * Simon follows Bert. This activity joins the Group #1 aggregate, and "orphans" the Group #2 aggregate (i.e., Group #2 - * aggregate no longer references any activity since that activity gets deleted and replaced with an updated one). The new - * states should be: - * - * Aggregate State: - * - * Group #1: Simon followed (Branden,Bert) [references Activity #2] - * Group #2: (Simon) followed Branden [references Activity #1 (deleted)] - * Group #3: (Simon) followed Bert [(no reference since Group #3 "claimed" the activity)] - * - * Activity Feed State: - * - * Activity #2: Simon followed Branden and Bert - */ - RestAPI.Following.follow(simon.restContext, bert.user.id, err => { + ActivityTestsUtil.collectAndGetActivityStream(simon.restContext, simon.user.id, null, (err, response) => { assert.ok(!err); - ActivityTestsUtil.collectAndGetActivityStream( - simon.restContext, - simon.user.id, - null, - (err, response) => { - assert.ok(!err); + /*! + * Simon follows Bert, again. The point of this test is to ensure that this activity doesn't rejoin with Group #2, + * thus resulting a new "Simon followed Branden" being delivered, as it is a redundant activity that is already + * represented by the activity being tracked by Group #1. + * + * Aggregate State: + * + * Group #1: Simon followed (Branden,Bert) [references Activity #3] + * Group #2: (Simon) followed Branden [references Activity #1 (deleted)] + * + * Activity Feed State: + * + * Activity #3: Simon followed Branden and Bert + * + * Therefore we should assert that Simon's activity feed contains only 1 single activity + */ + RestAPI.Following.follow(simon.restContext, branden.user.id, err => { + assert.ok(!err); - /*! - * Simon follows Bert, again. The point of this test is to ensure that this activity doesn't rejoin with Group #2, - * thus resulting a new "Simon followed Branden" being delivered, as it is a redundant activity that is already - * represented by the activity being tracked by Group #1. - * - * Aggregate State: - * - * Group #1: Simon followed (Branden,Bert) [references Activity #3] - * Group #2: (Simon) followed Branden [references Activity #1 (deleted)] - * - * Activity Feed State: - * - * Activity #3: Simon followed Branden and Bert - * - * Therefore we should assert that Simon's activity feed contains only 1 single activity - */ - RestAPI.Following.follow(simon.restContext, branden.user.id, err => { + ActivityTestsUtil.collectAndGetActivityStream( + simon.restContext, + simon.user.id, + null, + (err, response) => { assert.ok(!err); - - ActivityTestsUtil.collectAndGetActivityStream( - simon.restContext, - simon.user.id, - null, - (err, response) => { - assert.ok(!err); - assert.strictEqual(response.items.length, 1); - return callback(); - } - ); - }); - } - ); + assert.strictEqual(response.items.length, 1); + return callback(); + } + ); + }); }); - } - ); + }); + }); }); }); }); diff --git a/packages/oae-following/tests/test-following.js b/packages/oae-following/tests/test-following.js index 4b3b5b00d8..b32fdddd44 100644 --- a/packages/oae-following/tests/test-following.js +++ b/packages/oae-following/tests/test-following.js @@ -13,15 +13,13 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const ConfigTestsUtil = require('oae-config/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const TestsUtil = require('oae-tests/lib/util'); - -const FollowingTestsUtil = require('oae-following/lib/test/util'); +import * as ConfigTestsUtil from 'oae-config/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests/lib/util'; +import * as FollowingTestsUtil from 'oae-following/lib/test/util'; describe('Following', () => { let globalAdminOnTenantRestContext = null; @@ -38,24 +36,19 @@ describe('Following', () => { gtAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.gt.host); // Authenticate the global admin into a tenant so we can perform user-tenant requests with a global admin to test their access - RestAPI.Admin.loginOnTenant( - TestsUtil.createGlobalAdminRestContext(), - 'localhost', - null, - (err, ctx) => { - assert.ok(!err); - globalAdminOnTenantRestContext = ctx; - return callback(); - } - ); + RestAPI.Admin.loginOnTenant(TestsUtil.createGlobalAdminRestContext(), 'localhost', null, (err, ctx) => { + assert.ok(!err); + globalAdminOnTenantRestContext = ctx; + return callback(); + }); }); /*! - * Ensure that the given feeds have the same users (by id) and in the same order - * - * @param {User[]} oneFeedUsers One feed of users to compare with - * @param {User[]} otherFeedUsers The other feed of users to compare with - */ + * Ensure that the given feeds have the same users (by id) and in the same order + * + * @param {User[]} oneFeedUsers One feed of users to compare with + * @param {User[]} otherFeedUsers The other feed of users to compare with + */ const _assertFeedsEqual = function(oneFeedUsers, otherFeedUsers) { assert.ok(oneFeedUsers); assert.ok(otherFeedUsers); @@ -76,35 +69,23 @@ describe('Following', () => { const user = _.values(testUsers)[0]; // Verify clean empty response - RestAPI.Following.getFollowers( - user.restContext, - user.user.id, - null, - null, - (err, response) => { + RestAPI.Following.getFollowers(user.restContext, user.user.id, null, null, (err, response) => { + assert.ok(!err); + assert.ok(response); + assert.ok(response.results); + assert.strictEqual(response.results.length, 0); + assert.ok(!response.nextToken); + + // Verify clean empty response again + RestAPI.Following.getFollowing(user.restContext, user.user.id, null, null, (err, response) => { assert.ok(!err); assert.ok(response); assert.ok(response.results); assert.strictEqual(response.results.length, 0); assert.ok(!response.nextToken); - - // Verify clean empty response again - RestAPI.Following.getFollowing( - user.restContext, - user.user.id, - null, - null, - (err, response) => { - assert.ok(!err); - assert.ok(response); - assert.ok(response.results); - assert.strictEqual(response.results.length, 0); - assert.ok(!response.nextToken); - return callback(); - } - ); - } - ); + return callback(); + }); + }); }); }); @@ -151,98 +132,63 @@ describe('Following', () => { const publicUser = _.values(testUsers)[2]; const bert = _.values(testUsers)[3]; - RestAPI.User.updateUser( - privateUser.restContext, - privateUser.user.id, - { visibility: 'private' }, - err => { - assert.ok(!err); + RestAPI.User.updateUser(privateUser.restContext, privateUser.user.id, { visibility: 'private' }, err => { + assert.ok(!err); - RestAPI.User.updateUser( - loggedinUser.restContext, - loggedinUser.user.id, - { visibility: 'loggedin' }, - err => { - assert.ok(!err); + RestAPI.User.updateUser(loggedinUser.restContext, loggedinUser.user.id, { visibility: 'loggedin' }, err => { + assert.ok(!err); - // Verify anonymous can only see public user feeds - FollowingTestsUtil.assertNoFollowFeedAccess( - camAnonymousRestContext, - [privateUser.user.id, loggedinUser.user.id], - 401, - () => { - FollowingTestsUtil.assertHasFollowFeedAccess( - camAnonymousRestContext, - [publicUser.user.id], - () => { - // Verify gt admin can only see public user feeds - FollowingTestsUtil.assertNoFollowFeedAccess( - gtAdminRestContext, - [privateUser.user.id, loggedinUser.user.id], - 401, - () => { - FollowingTestsUtil.assertHasFollowFeedAccess( - gtAdminRestContext, - [publicUser.user.id], - () => { - // Verify bert can see only public and loggedin user feeds - FollowingTestsUtil.assertNoFollowFeedAccess( - bert.restContext, - [privateUser.user.id], - 401, - () => { - FollowingTestsUtil.assertHasFollowFeedAccess( - bert.restContext, - [publicUser.user.id, loggedinUser.user.id], - () => { - // Verify private user can see all feeds - FollowingTestsUtil.assertHasFollowFeedAccess( - privateUser.restContext, - [ - publicUser.user.id, - loggedinUser.user.id, - privateUser.user.id - ], - () => { - // Verify cam admin can see all feeds - FollowingTestsUtil.assertHasFollowFeedAccess( - camAdminRestContext, - [ - publicUser.user.id, - loggedinUser.user.id, - privateUser.user.id - ], - () => { - // Verify global admin can see all feeds - FollowingTestsUtil.assertHasFollowFeedAccess( - globalAdminOnTenantRestContext, - [ - publicUser.user.id, - loggedinUser.user.id, - privateUser.user.id - ], - callback - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + // Verify anonymous can only see public user feeds + FollowingTestsUtil.assertNoFollowFeedAccess( + camAnonymousRestContext, + [privateUser.user.id, loggedinUser.user.id], + 401, + () => { + FollowingTestsUtil.assertHasFollowFeedAccess(camAnonymousRestContext, [publicUser.user.id], () => { + // Verify gt admin can only see public user feeds + FollowingTestsUtil.assertNoFollowFeedAccess( + gtAdminRestContext, + [privateUser.user.id, loggedinUser.user.id], + 401, + () => { + FollowingTestsUtil.assertHasFollowFeedAccess(gtAdminRestContext, [publicUser.user.id], () => { + // Verify bert can see only public and loggedin user feeds + FollowingTestsUtil.assertNoFollowFeedAccess(bert.restContext, [privateUser.user.id], 401, () => { + FollowingTestsUtil.assertHasFollowFeedAccess( + bert.restContext, + [publicUser.user.id, loggedinUser.user.id], + () => { + // Verify private user can see all feeds + FollowingTestsUtil.assertHasFollowFeedAccess( + privateUser.restContext, + [publicUser.user.id, loggedinUser.user.id, privateUser.user.id], + () => { + // Verify cam admin can see all feeds + FollowingTestsUtil.assertHasFollowFeedAccess( + camAdminRestContext, + [publicUser.user.id, loggedinUser.user.id, privateUser.user.id], + () => { + // Verify global admin can see all feeds + FollowingTestsUtil.assertHasFollowFeedAccess( + globalAdminOnTenantRestContext, + [publicUser.user.id, loggedinUser.user.id, privateUser.user.id], + callback + ); + } + ); + } + ); + } + ); + }); + }); + } + ); + }); } ); - } - ); + }); + }); }); }); @@ -256,53 +202,29 @@ describe('Following', () => { const bert = _.values(testUsers)[0]; // Verify a non-valid id - RestAPI.Following.getFollowers( - bert.restContext, - 'not-a-valid-id', - null, - null, - (err, response) => { + RestAPI.Following.getFollowers(bert.restContext, 'not-a-valid-id', null, null, (err, response) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + + // Verify a resource id that is not a user + RestAPI.Following.getFollowers(bert.restContext, 'g:not-a:user-id', null, null, (err, response) => { assert.ok(err); assert.strictEqual(err.code, 400); - // Verify a resource id that is not a user - RestAPI.Following.getFollowers( - bert.restContext, - 'g:not-a:user-id', - null, - null, - (err, response) => { - assert.ok(err); - assert.strictEqual(err.code, 400); + // Verify a non-existing user + RestAPI.Following.getFollowers(bert.restContext, 'u:cam:nonExistentUserId', null, null, (err, response) => { + assert.ok(err); + assert.strictEqual(err.code, 404); - // Verify a non-existing user - RestAPI.Following.getFollowers( - bert.restContext, - 'u:cam:nonExistentUserId', - null, - null, - (err, response) => { - assert.ok(err); - assert.strictEqual(err.code, 404); - - // Sanity check a valid fetch - RestAPI.Following.getFollowers( - bert.restContext, - bert.user.id, - null, - null, - (err, response) => { - assert.ok(!err); - assert.ok(response); - return callback(); - } - ); - } - ); - } - ); - } - ); + // Sanity check a valid fetch + RestAPI.Following.getFollowers(bert.restContext, bert.user.id, null, null, (err, response) => { + assert.ok(!err); + assert.ok(response); + return callback(); + }); + }); + }); + }); }); }); @@ -316,53 +238,29 @@ describe('Following', () => { const bert = _.values(testUsers)[0]; // Verify a non-valid id - RestAPI.Following.getFollowing( - bert.restContext, - 'not-a-valid-id', - null, - null, - (err, response) => { + RestAPI.Following.getFollowing(bert.restContext, 'not-a-valid-id', null, null, (err, response) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + + // Verify a resource id that is not a user + RestAPI.Following.getFollowing(bert.restContext, 'g:not-a:user-id', null, null, (err, response) => { assert.ok(err); assert.strictEqual(err.code, 400); - // Verify a resource id that is not a user - RestAPI.Following.getFollowing( - bert.restContext, - 'g:not-a:user-id', - null, - null, - (err, response) => { - assert.ok(err); - assert.strictEqual(err.code, 400); + // Verify a non-existing user + RestAPI.Following.getFollowing(bert.restContext, 'u:cam:nonExistentUserId', null, null, (err, response) => { + assert.ok(err); + assert.strictEqual(err.code, 404); - // Verify a non-existing user - RestAPI.Following.getFollowing( - bert.restContext, - 'u:cam:nonExistentUserId', - null, - null, - (err, response) => { - assert.ok(err); - assert.strictEqual(err.code, 404); - - // Sanity check a valid fetch - RestAPI.Following.getFollowing( - bert.restContext, - bert.user.id, - null, - null, - (err, response) => { - assert.ok(!err); - assert.ok(response); - return callback(); - } - ); - } - ); - } - ); - } - ); + // Sanity check a valid fetch + RestAPI.Following.getFollowing(bert.restContext, bert.user.id, null, null, (err, response) => { + assert.ok(!err); + assert.ok(response); + return callback(); + }); + }); + }); + }); }); }); @@ -392,23 +290,17 @@ describe('Following', () => { assert.strictEqual(err.code, 404); // Ensure no following took place - RestAPI.Following.getFollowing( - bert.restContext, - bert.user.id, - null, - null, - (err, response) => { - assert.ok(response); - assert.ok(response.results); - assert.strictEqual(response.results.length, 0); + RestAPI.Following.getFollowing(bert.restContext, bert.user.id, null, null, (err, response) => { + assert.ok(response); + assert.ok(response.results); + assert.strictEqual(response.results.length, 0); - // Sanity check inputs - RestAPI.Following.follow(bert.restContext, simon.user.id, err => { - assert.ok(!err); - return callback(); - }); - } - ); + // Sanity check inputs + RestAPI.Following.follow(bert.restContext, simon.user.id, err => { + assert.ok(!err); + return callback(); + }); + }); }); }); }); @@ -421,74 +313,58 @@ describe('Following', () => { it('verify follow authorization', callback => { TestsUtil.setupMultiTenantPrivacyEntities((publicTenant0, publicTenant1, privateTenant0) => { // Ensure a user cannot follow themself - RestAPI.Following.follow( - publicTenant0.publicUser.restContext, - publicTenant0.publicUser.user.id, - err => { + RestAPI.Following.follow(publicTenant0.publicUser.restContext, publicTenant0.publicUser.user.id, err => { + assert.ok(err); + assert.strictEqual(err.code, 400); + + // Ensure a user cannot follow a public user from an external private tenant + RestAPI.Following.follow(publicTenant0.publicUser.restContext, privateTenant0.publicUser.user.id, err => { assert.ok(err); - assert.strictEqual(err.code, 400); + assert.strictEqual(err.code, 401); + + // Ensure a user cannot follow a loggedin user from an external public tenant + RestAPI.Following.follow(publicTenant0.publicUser.restContext, publicTenant1.loggedinUser.user.id, err => { + assert.ok(err); + assert.strictEqual(err.code, 401); - // Ensure a user cannot follow a public user from an external private tenant - RestAPI.Following.follow( - publicTenant0.publicUser.restContext, - privateTenant0.publicUser.user.id, - err => { + // Ensure a user cannot follow a private user from an external public tenant + RestAPI.Following.follow(publicTenant0.publicUser.restContext, publicTenant1.privateUser.user.id, err => { assert.ok(err); assert.strictEqual(err.code, 401); - // Ensure a user cannot follow a loggedin user from an external public tenant - RestAPI.Following.follow( + // Verify that the publicTenant0 public user is still not following anyone + RestAPI.Following.getFollowing( publicTenant0.publicUser.restContext, - publicTenant1.loggedinUser.user.id, - err => { - assert.ok(err); - assert.strictEqual(err.code, 401); + publicTenant0.publicUser.user.id, + null, + null, + (err, response) => { + assert.ok(!err); + assert.ok(response); + assert.ok(response.results); + assert.strictEqual(response.results.length, 0); - // Ensure a user cannot follow a private user from an external public tenant + // Sanity check can follow public user from external public tenant RestAPI.Following.follow( publicTenant0.publicUser.restContext, - publicTenant1.privateUser.user.id, + publicTenant1.publicUser.user.id, err => { - assert.ok(err); - assert.strictEqual(err.code, 401); - - // Verify that the publicTenant0 public user is still not following anyone - RestAPI.Following.getFollowing( - publicTenant0.publicUser.restContext, + assert.ok(!err); + return FollowingTestsUtil.assertFollows( publicTenant0.publicUser.user.id, - null, - null, - (err, response) => { - assert.ok(!err); - assert.ok(response); - assert.ok(response.results); - assert.strictEqual(response.results.length, 0); - - // Sanity check can follow public user from external public tenant - RestAPI.Following.follow( - publicTenant0.publicUser.restContext, - publicTenant1.publicUser.user.id, - err => { - assert.ok(!err); - return FollowingTestsUtil.assertFollows( - publicTenant0.publicUser.user.id, - publicTenant0.publicUser.restContext, - publicTenant1.publicUser.user.id, - publicTenant1.publicUser.restContext, - callback - ); - } - ); - } + publicTenant0.publicUser.restContext, + publicTenant1.publicUser.user.id, + publicTenant1.publicUser.restContext, + callback ); } ); } ); - } - ); - } - ); + }); + }); + }); + }); }); }); @@ -498,47 +374,39 @@ describe('Following', () => { it('verify unfollow authorization', callback => { TestsUtil.setupMultiTenantPrivacyEntities((publicTenant0, publicTenant1, privateTenant0) => { // Perform a follow from publicTenant0 to publicTenant1 - RestAPI.Following.follow( - publicTenant0.publicUser.restContext, - publicTenant1.publicUser.user.id, - err => { - assert.ok(!err); + RestAPI.Following.follow(publicTenant0.publicUser.restContext, publicTenant1.publicUser.user.id, err => { + assert.ok(!err); + + // Now make publicTenant1 private + ConfigTestsUtil.updateConfigAndWait( + TestsUtil.createGlobalAdminRestContext(), + publicTenant1.tenant.alias, + { 'oae-tenants/tenantprivacy/tenantprivate': true }, + err => { + assert.ok(!err); - // Now make publicTenant1 private - ConfigTestsUtil.updateConfigAndWait( - TestsUtil.createGlobalAdminRestContext(), - publicTenant1.tenant.alias, - { 'oae-tenants/tenantprivacy/tenantprivate': true }, - err => { + // Now make sure we can unfollow the user in the newly private tenant + RestAPI.Following.unfollow(publicTenant0.publicUser.restContext, publicTenant1.publicUser.user.id, err => { assert.ok(!err); - // Now make sure we can unfollow the user in the newly private tenant - RestAPI.Following.unfollow( + // Ensure that the following user is not following anyone anymore + RestAPI.Following.getFollowing( publicTenant0.publicUser.restContext, - publicTenant1.publicUser.user.id, - err => { + publicTenant0.publicUser.user.id, + null, + null, + (err, response) => { assert.ok(!err); - - // Ensure that the following user is not following anyone anymore - RestAPI.Following.getFollowing( - publicTenant0.publicUser.restContext, - publicTenant0.publicUser.user.id, - null, - null, - (err, response) => { - assert.ok(!err); - assert.ok(response); - assert.ok(response.results); - assert.strictEqual(response.results.length, 0); - return callback(); - } - ); + assert.ok(response); + assert.ok(response.results); + assert.strictEqual(response.results.length, 0); + return callback(); } ); - } - ); - } - ); + }); + } + ); + }); }); }); @@ -595,56 +463,44 @@ describe('Following', () => { // Make the follower follow all the 9 following users FollowingTestsUtil.followAll(follower.restContext, followingUserIds, () => { // Get the natural following order - RestAPI.Following.getFollowing( - follower.restContext, - follower.user.id, - null, - 9, - (err, response) => { - assert.ok(!err); - assert.strictEqual(response.results.length, 9); + RestAPI.Following.getFollowing(follower.restContext, follower.user.id, null, 9, (err, response) => { + assert.ok(!err); + assert.strictEqual(response.results.length, 9); + + const followingUsers = response.results; - const followingUsers = response.results; + // Get the first 2, ensure we were restricted by the limit + RestAPI.Following.getFollowing(follower.restContext, follower.user.id, null, 2, (err, response) => { + assert.ok(!err); + _assertFeedsEqual(response.results, followingUsers.slice(0, 2)); - // Get the first 2, ensure we were restricted by the limit + // Get the next 2, ensure it is the next 2-item-slice of the following array RestAPI.Following.getFollowing( follower.restContext, follower.user.id, - null, + response.nextToken, 2, (err, response) => { assert.ok(!err); - _assertFeedsEqual(response.results, followingUsers.slice(0, 2)); + _assertFeedsEqual(response.results, followingUsers.slice(2, 4)); - // Get the next 2, ensure it is the next 2-item-slice of the following array + // Now overflow the list RestAPI.Following.getFollowing( follower.restContext, follower.user.id, response.nextToken, - 2, + 8, (err, response) => { assert.ok(!err); - _assertFeedsEqual(response.results, followingUsers.slice(2, 4)); - - // Now overflow the list - RestAPI.Following.getFollowing( - follower.restContext, - follower.user.id, - response.nextToken, - 8, - (err, response) => { - assert.ok(!err); - assert.ok(!response.nextToken); - _assertFeedsEqual(response.results, followingUsers.slice(4)); - return callback(); - } - ); + assert.ok(!response.nextToken); + _assertFeedsEqual(response.results, followingUsers.slice(4)); + return callback(); } ); } ); - } - ); + }); + }); }); }); }); @@ -664,56 +520,44 @@ describe('Following', () => { // Make the follower follow all the 9 following users FollowingTestsUtil.followByAll(followed.user.id, followers, () => { // Get the natural following order - RestAPI.Following.getFollowers( - followed.restContext, - followed.user.id, - null, - 9, - (err, response) => { - assert.ok(!err); - assert.strictEqual(response.results.length, 9); + RestAPI.Following.getFollowers(followed.restContext, followed.user.id, null, 9, (err, response) => { + assert.ok(!err); + assert.strictEqual(response.results.length, 9); + + const followerUsers = response.results; - const followerUsers = response.results; + // Get the first 2, ensure we were restricted by the limit + RestAPI.Following.getFollowers(followed.restContext, followed.user.id, null, 2, (err, response) => { + assert.ok(!err); + _assertFeedsEqual(response.results, followerUsers.slice(0, 2)); - // Get the first 2, ensure we were restricted by the limit + // Get the next 2, ensure it is the next 2-item-slice of the following array RestAPI.Following.getFollowers( followed.restContext, followed.user.id, - null, + response.nextToken, 2, (err, response) => { assert.ok(!err); - _assertFeedsEqual(response.results, followerUsers.slice(0, 2)); + _assertFeedsEqual(response.results, followerUsers.slice(2, 4)); - // Get the next 2, ensure it is the next 2-item-slice of the following array + // Now overflow the list RestAPI.Following.getFollowers( followed.restContext, followed.user.id, response.nextToken, - 2, + 8, (err, response) => { assert.ok(!err); - _assertFeedsEqual(response.results, followerUsers.slice(2, 4)); - - // Now overflow the list - RestAPI.Following.getFollowers( - followed.restContext, - followed.user.id, - response.nextToken, - 8, - (err, response) => { - assert.ok(!err); - assert.ok(!response.nextToken); - _assertFeedsEqual(response.results, followerUsers.slice(4)); - return callback(); - } - ); + assert.ok(!response.nextToken); + _assertFeedsEqual(response.results, followerUsers.slice(4)); + return callback(); } ); } ); - } - ); + }); + }); }); }); }); diff --git a/packages/oae-following/tests/test-profile-decorator.js b/packages/oae-following/tests/test-profile-decorator.js index 7f693d373f..1659524915 100644 --- a/packages/oae-following/tests/test-profile-decorator.js +++ b/packages/oae-following/tests/test-profile-decorator.js @@ -13,15 +13,13 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const ConfigTestsUtil = require('oae-config/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const TestsUtil = require('oae-tests/lib/util'); - -const FollowingTestsUtil = require('oae-following/lib/test/util'); +import * as ConfigTestsUtil from 'oae-config/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests/lib/util'; +import * as FollowingTestsUtil from 'oae-following/lib/test/util'; describe('Following Profile Decorator', () => { /** @@ -30,126 +28,108 @@ describe('Following Profile Decorator', () => { it('verify following decorator for different visibility scenarios', callback => { TestsUtil.setupMultiTenantPrivacyEntities((publicTenant0, publicTenant1, privateTenant0) => { // Ensure anonymous doesn't see a following property - RestAPI.User.getUser( - publicTenant0.anonymousRestContext, - publicTenant0.publicUser.user.id, - (err, user) => { + RestAPI.User.getUser(publicTenant0.anonymousRestContext, publicTenant0.publicUser.user.id, (err, user) => { + assert.ok(!err); + assert.ok(!user.following); + + // Ensure a user doesn't see a following property on their own profile + RestAPI.User.getUser(publicTenant0.publicUser.restContext, publicTenant0.publicUser.user.id, (err, user) => { assert.ok(!err); assert.ok(!user.following); - // Ensure a user doesn't see a following property on their own profile + // Ensure user profile reports a user can follow a user from their tenant RestAPI.User.getUser( publicTenant0.publicUser.restContext, - publicTenant0.publicUser.user.id, + publicTenant0.loggedinUser.user.id, (err, user) => { assert.ok(!err); - assert.ok(!user.following); + assert.ok(user.following); + assert.strictEqual(user.following.canFollow, true); + assert.strictEqual(user.following.isFollowing, false); - // Ensure user profile reports a user can follow a user from their tenant + // Ensure user profile reports a user can follow a public user from an external public tenant RestAPI.User.getUser( publicTenant0.publicUser.restContext, - publicTenant0.loggedinUser.user.id, + publicTenant1.publicUser.user.id, (err, user) => { assert.ok(!err); assert.ok(user.following); assert.strictEqual(user.following.canFollow, true); assert.strictEqual(user.following.isFollowing, false); - // Ensure user profile reports a user can follow a public user from an external public tenant + // Ensure user profile reports a user cannot follow a public user from an external private tenant RestAPI.User.getUser( publicTenant0.publicUser.restContext, - publicTenant1.publicUser.user.id, + privateTenant0.publicUser.user.id, (err, user) => { assert.ok(!err); assert.ok(user.following); - assert.strictEqual(user.following.canFollow, true); + assert.strictEqual(user.following.canFollow, false); assert.strictEqual(user.following.isFollowing, false); - // Ensure user profile reports a user cannot follow a public user from an external private tenant - RestAPI.User.getUser( - publicTenant0.publicUser.restContext, - privateTenant0.publicUser.user.id, - (err, user) => { + // Make privateTenant0 public so we can do a cross-tenant follow + ConfigTestsUtil.updateConfigAndWait( + TestsUtil.createGlobalAdminRestContext(), + privateTenant0.tenant.alias, + { 'oae-tenants/tenantprivacy/tenantprivate': false }, + err => { assert.ok(!err); - assert.ok(user.following); - assert.strictEqual(user.following.canFollow, false); - assert.strictEqual(user.following.isFollowing, false); - - // Make privateTenant0 public so we can do a cross-tenant follow - ConfigTestsUtil.updateConfigAndWait( - TestsUtil.createGlobalAdminRestContext(), - privateTenant0.tenant.alias, - { 'oae-tenants/tenantprivacy/tenantprivate': false }, - err => { - assert.ok(!err); - const followedIds = [ - publicTenant0.loggedinUser.user.id, - publicTenant1.publicUser.user.id, - privateTenant0.publicUser.user.id - ]; + const followedIds = [ + publicTenant0.loggedinUser.user.id, + publicTenant1.publicUser.user.id, + privateTenant0.publicUser.user.id + ]; - // Follow the test subject users - FollowingTestsUtil.followAll( - publicTenant0.publicUser.restContext, - followedIds, - () => { - // Make the tenant private again - ConfigTestsUtil.updateConfigAndWait( - TestsUtil.createGlobalAdminRestContext(), - privateTenant0.tenant.alias, - { 'oae-tenants/tenantprivacy/tenantprivate': true }, - err => { - assert.ok(!err); + // Follow the test subject users + FollowingTestsUtil.followAll(publicTenant0.publicUser.restContext, followedIds, () => { + // Make the tenant private again + ConfigTestsUtil.updateConfigAndWait( + TestsUtil.createGlobalAdminRestContext(), + privateTenant0.tenant.alias, + { 'oae-tenants/tenantprivacy/tenantprivate': true }, + err => { + assert.ok(!err); - // Ensure user profile now reports that they are followed, and can no longer be followed - RestAPI.User.getUser( - publicTenant0.publicUser.restContext, - publicTenant0.loggedinUser.user.id, - (err, user) => { - assert.ok(!err); - assert.ok(user.following); - assert.strictEqual(user.following.canFollow, false); - assert.strictEqual(user.following.isFollowing, true); + // Ensure user profile now reports that they are followed, and can no longer be followed + RestAPI.User.getUser( + publicTenant0.publicUser.restContext, + publicTenant0.loggedinUser.user.id, + (err, user) => { + assert.ok(!err); + assert.ok(user.following); + assert.strictEqual(user.following.canFollow, false); + assert.strictEqual(user.following.isFollowing, true); - // Ensure user profile now reports that they are followed, and can no longer be followed - RestAPI.User.getUser( - publicTenant0.publicUser.restContext, - publicTenant1.publicUser.user.id, - (err, user) => { - assert.ok(!err); - assert.ok(user.following); - assert.strictEqual(user.following.canFollow, false); - assert.strictEqual(user.following.isFollowing, true); + // Ensure user profile now reports that they are followed, and can no longer be followed + RestAPI.User.getUser( + publicTenant0.publicUser.restContext, + publicTenant1.publicUser.user.id, + (err, user) => { + assert.ok(!err); + assert.ok(user.following); + assert.strictEqual(user.following.canFollow, false); + assert.strictEqual(user.following.isFollowing, true); - // Ensure user profile now reports the user from the private tenant is being followed, and still cannot be followed - RestAPI.User.getUser( - publicTenant0.publicUser.restContext, - privateTenant0.publicUser.user.id, - (err, user) => { - assert.ok(!err); - assert.ok(user.following); - assert.strictEqual( - user.following.canFollow, - false - ); - assert.strictEqual( - user.following.isFollowing, - true - ); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + // Ensure user profile now reports the user from the private tenant is being followed, and still cannot be followed + RestAPI.User.getUser( + publicTenant0.publicUser.restContext, + privateTenant0.publicUser.user.id, + (err, user) => { + assert.ok(!err); + assert.ok(user.following); + assert.strictEqual(user.following.canFollow, false); + assert.strictEqual(user.following.isFollowing, true); + return callback(); + } + ); + } + ); + } + ); + } + ); + }); } ); } @@ -158,8 +138,8 @@ describe('Following Profile Decorator', () => { ); } ); - } - ); + }); + }); }); }); }); diff --git a/packages/oae-following/tests/test-search.js b/packages/oae-following/tests/test-search.js index 73d60f68b9..dd1be4d6b8 100644 --- a/packages/oae-following/tests/test-search.js +++ b/packages/oae-following/tests/test-search.js @@ -13,15 +13,13 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests/lib/util'); - -const FollowingTestsUtil = require('oae-following/lib/test/util'); +import * as RestAPI from 'oae-rest'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests/lib/util'; +import * as FollowingTestsUtil from 'oae-following/lib/test/util'; let globalAdminOnTenantRestContext = null; let camAnonymousRestContext = null; @@ -42,16 +40,11 @@ describe('Following Search', () => { gtAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.gt.host); // Authenticate the global admin into a tenant so we can perform user-tenant requests with a global admin to test their access - RestAPI.Admin.loginOnTenant( - TestsUtil.createGlobalAdminRestContext(), - 'localhost', - null, - (err, ctx) => { - assert.ok(!err); - globalAdminOnTenantRestContext = ctx; - return callback(); - } - ); + RestAPI.Admin.loginOnTenant(TestsUtil.createGlobalAdminRestContext(), 'localhost', null, (err, ctx) => { + assert.ok(!err); + globalAdminOnTenantRestContext = ctx; + return callback(); + }); }); /** @@ -62,34 +55,22 @@ describe('Following Search', () => { assert.ok(!err); const user = _.values(testUsers)[0]; - RestAPI.Search.search( - user.restContext, - 'following', - [user.user.id], - null, - (err, response) => { + RestAPI.Search.search(user.restContext, 'following', [user.user.id], null, (err, response) => { + assert.ok(!err); + assert.ok(response); + assert.strictEqual(response.total, 0); + assert.ok(response.results); + assert.strictEqual(response.results.length, 0); + + RestAPI.Search.search(user.restContext, 'followers', [user.user.id], null, (err, response) => { assert.ok(!err); assert.ok(response); assert.strictEqual(response.total, 0); assert.ok(response.results); assert.strictEqual(response.results.length, 0); - - RestAPI.Search.search( - user.restContext, - 'followers', - [user.user.id], - null, - (err, response) => { - assert.ok(!err); - assert.ok(response); - assert.strictEqual(response.total, 0); - assert.ok(response.results); - assert.strictEqual(response.results.length, 0); - return callback(); - } - ); - } - ); + return callback(); + }); + }); }); }); @@ -102,58 +83,34 @@ describe('Following Search', () => { const user = _.values(testUsers)[0]; // Ensure failure with a non-valid resource id - RestAPI.Search.search( - user.restContext, - 'following', - ['not-a-valid-id'], - null, - (err, response) => { + RestAPI.Search.search(user.restContext, 'following', ['not-a-valid-id'], null, (err, response) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.ok(!response); + + // Ensure failure with group id instead of user id + RestAPI.Search.search(user.restContext, 'following', ['g:not-a:user-id'], null, (err, response) => { assert.ok(err); assert.strictEqual(err.code, 400); assert.ok(!response); - // Ensure failure with group id instead of user id - RestAPI.Search.search( - user.restContext, - 'following', - ['g:not-a:user-id'], - null, - (err, response) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.ok(!response); - - // Ensure failure with non-existent user id - RestAPI.Search.search( - user.restContext, - 'following', - ['u:cam:nonExistentUserId'], - null, - (err, response) => { - assert.ok(err); - assert.strictEqual(err.code, 404); - assert.ok(!response); + // Ensure failure with non-existent user id + RestAPI.Search.search(user.restContext, 'following', ['u:cam:nonExistentUserId'], null, (err, response) => { + assert.ok(err); + assert.strictEqual(err.code, 404); + assert.ok(!response); - // Sanity check a valid search - RestAPI.Search.search( - user.restContext, - 'following', - [user.user.id], - null, - (err, response) => { - assert.ok(response); - assert.strictEqual(response.total, 0); - assert.ok(response.results); - assert.strictEqual(response.results.length, 0); - return callback(); - } - ); - } - ); - } - ); - } - ); + // Sanity check a valid search + RestAPI.Search.search(user.restContext, 'following', [user.user.id], null, (err, response) => { + assert.ok(response); + assert.strictEqual(response.total, 0); + assert.ok(response.results); + assert.strictEqual(response.results.length, 0); + return callback(); + }); + }); + }); + }); }); }); @@ -166,58 +123,34 @@ describe('Following Search', () => { const user = _.values(testUsers)[0]; // Ensure failure with a non-valid resource id - RestAPI.Search.search( - user.restContext, - 'followers', - ['not-a-valid-id'], - null, - (err, response) => { + RestAPI.Search.search(user.restContext, 'followers', ['not-a-valid-id'], null, (err, response) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.ok(!response); + + // Ensure failure with group id instead of user id + RestAPI.Search.search(user.restContext, 'followers', ['g:not-a:user-id'], null, (err, response) => { assert.ok(err); assert.strictEqual(err.code, 400); assert.ok(!response); - // Ensure failure with group id instead of user id - RestAPI.Search.search( - user.restContext, - 'followers', - ['g:not-a:user-id'], - null, - (err, response) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.ok(!response); - - // Ensure failure with non-existent user id - RestAPI.Search.search( - user.restContext, - 'followers', - ['u:cam:nonExistentUserId'], - null, - (err, response) => { - assert.ok(err); - assert.strictEqual(err.code, 404); - assert.ok(!response); + // Ensure failure with non-existent user id + RestAPI.Search.search(user.restContext, 'followers', ['u:cam:nonExistentUserId'], null, (err, response) => { + assert.ok(err); + assert.strictEqual(err.code, 404); + assert.ok(!response); - // Sanity check a valid search - RestAPI.Search.search( - user.restContext, - 'followers', - [user.user.id], - null, - (err, response) => { - assert.ok(response); - assert.strictEqual(response.total, 0); - assert.ok(response.results); - assert.strictEqual(response.results.length, 0); - return callback(); - } - ); - } - ); - } - ); - } - ); + // Sanity check a valid search + RestAPI.Search.search(user.restContext, 'followers', [user.user.id], null, (err, response) => { + assert.ok(response); + assert.strictEqual(response.total, 0); + assert.ok(response.results); + assert.strictEqual(response.results.length, 0); + return callback(); + }); + }); + }); + }); }); }); @@ -269,98 +202,63 @@ describe('Following Search', () => { const publicUser = _.values(testUsers)[2]; const bert = _.values(testUsers)[3]; - RestAPI.User.updateUser( - privateUser.restContext, - privateUser.user.id, - { visibility: 'private' }, - err => { - assert.ok(!err); + RestAPI.User.updateUser(privateUser.restContext, privateUser.user.id, { visibility: 'private' }, err => { + assert.ok(!err); - RestAPI.User.updateUser( - loggedinUser.restContext, - loggedinUser.user.id, - { visibility: 'loggedin' }, - err => { - assert.ok(!err); + RestAPI.User.updateUser(loggedinUser.restContext, loggedinUser.user.id, { visibility: 'loggedin' }, err => { + assert.ok(!err); - // Verify anonymous can only see public follow searches - FollowingTestsUtil.assertNoSearchFeedAccess( - camAnonymousRestContext, - [privateUser.user.id, loggedinUser.user.id], - 401, - () => { - FollowingTestsUtil.assertHasFollowFeedAccess( - camAnonymousRestContext, - [publicUser.user.id], - () => { - // Verify gt admin can only see public follow searches - FollowingTestsUtil.assertNoSearchFeedAccess( - gtAdminRestContext, - [privateUser.user.id, loggedinUser.user.id], - 401, - () => { - FollowingTestsUtil.assertHasSearchFeedAccess( - gtAdminRestContext, - [publicUser.user.id], - () => { - // Verify bert can see only public and loggedin follow searches - FollowingTestsUtil.assertNoSearchFeedAccess( - bert.restContext, - [privateUser.user.id], - 401, - () => { - FollowingTestsUtil.assertHasSearchFeedAccess( - bert.restContext, - [publicUser.user.id, loggedinUser.user.id], - () => { - // Verify private user can see follow searches - FollowingTestsUtil.assertHasSearchFeedAccess( - privateUser.restContext, - [ - publicUser.user.id, - loggedinUser.user.id, - privateUser.user.id - ], - () => { - // Verify cam admin can see follow searches - FollowingTestsUtil.assertHasSearchFeedAccess( - camAdminRestContext, - [ - publicUser.user.id, - loggedinUser.user.id, - privateUser.user.id - ], - () => { - // Verify global admin can see follow searches - FollowingTestsUtil.assertHasSearchFeedAccess( - globalAdminOnTenantRestContext, - [ - publicUser.user.id, - loggedinUser.user.id, - privateUser.user.id - ], - callback - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + // Verify anonymous can only see public follow searches + FollowingTestsUtil.assertNoSearchFeedAccess( + camAnonymousRestContext, + [privateUser.user.id, loggedinUser.user.id], + 401, + () => { + FollowingTestsUtil.assertHasFollowFeedAccess(camAnonymousRestContext, [publicUser.user.id], () => { + // Verify gt admin can only see public follow searches + FollowingTestsUtil.assertNoSearchFeedAccess( + gtAdminRestContext, + [privateUser.user.id, loggedinUser.user.id], + 401, + () => { + FollowingTestsUtil.assertHasSearchFeedAccess(gtAdminRestContext, [publicUser.user.id], () => { + // Verify bert can see only public and loggedin follow searches + FollowingTestsUtil.assertNoSearchFeedAccess(bert.restContext, [privateUser.user.id], 401, () => { + FollowingTestsUtil.assertHasSearchFeedAccess( + bert.restContext, + [publicUser.user.id, loggedinUser.user.id], + () => { + // Verify private user can see follow searches + FollowingTestsUtil.assertHasSearchFeedAccess( + privateUser.restContext, + [publicUser.user.id, loggedinUser.user.id, privateUser.user.id], + () => { + // Verify cam admin can see follow searches + FollowingTestsUtil.assertHasSearchFeedAccess( + camAdminRestContext, + [publicUser.user.id, loggedinUser.user.id, privateUser.user.id], + () => { + // Verify global admin can see follow searches + FollowingTestsUtil.assertHasSearchFeedAccess( + globalAdminOnTenantRestContext, + [publicUser.user.id, loggedinUser.user.id, privateUser.user.id], + callback + ); + } + ); + } + ); + } + ); + }); + }); + } + ); + }); } ); - } - ); + }); + }); }); }); diff --git a/packages/oae-google-analytics/config/google-analytics.js b/packages/oae-google-analytics/config/google-analytics.js index 81e694ed17..99631412cb 100644 --- a/packages/oae-google-analytics/config/google-analytics.js +++ b/packages/oae-google-analytics/config/google-analytics.js @@ -13,18 +13,22 @@ * permissions and limitations under the License. */ -var Fields = require('oae-config/lib/fields'); +import * as Fields from 'oae-config/lib/fields'; module.exports = { - 'title': 'OAE Google Analytics Module', - 'google-analytics': { - 'name': 'Google Analytics configuration', - 'description': 'Google Analytics configuration', - 'elements': { - 'globalEnabled': new Fields.Bool('Global GA enabled', 'Global Google Analytics enabled', false, {'tenantOverride': false}), - 'globalTrackingId': new Fields.Text('Global GA tracking-ID', 'The Global Google Analytics tracking-ID', '', {'tenantOverride': false}), - 'tenantEnabled': new Fields.Bool('Tenant GA enabled', 'Google Analytics enabled for tenant', false), - 'tenantTrackingId': new Fields.Text('Tenant GA tracking-ID', 'The Google Analytics tenant tracking-ID', '') - } + title: 'OAE Google Analytics Module', + 'google-analytics': { + name: 'Google Analytics configuration', + description: 'Google Analytics configuration', + elements: { + globalEnabled: new Fields.Bool('Global GA enabled', 'Global Google Analytics enabled', false, { + tenantOverride: false + }), + globalTrackingId: new Fields.Text('Global GA tracking-ID', 'The Global Google Analytics tracking-ID', '', { + tenantOverride: false + }), + tenantEnabled: new Fields.Bool('Tenant GA enabled', 'Google Analytics enabled for tenant', false), + tenantTrackingId: new Fields.Text('Tenant GA tracking-ID', 'The Google Analytics tenant tracking-ID', '') } + } }; diff --git a/packages/oae-google-analytics/tests/test-google-analytics.js b/packages/oae-google-analytics/tests/test-google-analytics.js index 77ba97cd0a..329faec2f7 100644 --- a/packages/oae-google-analytics/tests/test-google-analytics.js +++ b/packages/oae-google-analytics/tests/test-google-analytics.js @@ -13,10 +13,10 @@ * visibilitys and limitations under the License. */ -const assert = require('assert'); +import assert from 'assert'; -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; describe('Google Analytics', () => { // Rest context that can be used every time we need to make a request as an anonymous user @@ -56,43 +56,19 @@ describe('Google Analytics', () => { RestAPI.Config.getTenantConfig(camAdminRestContext, null, (err, config) => { assert.ok(!err); assert.ok(config); - assert.strictEqual( - config['oae-google-analytics']['google-analytics'].globalEnabled, - false - ); - assert.strictEqual( - config['oae-google-analytics']['google-analytics'].globalTrackingId, - '' - ); - assert.strictEqual( - config['oae-google-analytics']['google-analytics'].tenantEnabled, - false - ); - assert.strictEqual( - config['oae-google-analytics']['google-analytics'].tenantTrackingId, - '' - ); + assert.strictEqual(config['oae-google-analytics']['google-analytics'].globalEnabled, false); + assert.strictEqual(config['oae-google-analytics']['google-analytics'].globalTrackingId, ''); + assert.strictEqual(config['oae-google-analytics']['google-analytics'].tenantEnabled, false); + assert.strictEqual(config['oae-google-analytics']['google-analytics'].tenantTrackingId, ''); // Check that the Google Analytics config values are available in the config feed for an anonymous user RestAPI.Config.getTenantConfig(anonymousRestContext, null, (err, config) => { assert.ok(!err); assert.ok(config); - assert.strictEqual( - config['oae-google-analytics']['google-analytics'].globalEnabled, - false - ); - assert.strictEqual( - config['oae-google-analytics']['google-analytics'].globalTrackingId, - '' - ); - assert.strictEqual( - config['oae-google-analytics']['google-analytics'].tenantEnabled, - false - ); - assert.strictEqual( - config['oae-google-analytics']['google-analytics'].tenantTrackingId, - '' - ); + assert.strictEqual(config['oae-google-analytics']['google-analytics'].globalEnabled, false); + assert.strictEqual(config['oae-google-analytics']['google-analytics'].globalTrackingId, ''); + assert.strictEqual(config['oae-google-analytics']['google-analytics'].tenantEnabled, false); + assert.strictEqual(config['oae-google-analytics']['google-analytics'].tenantTrackingId, ''); callback(); }); }); diff --git a/packages/oae-jitsi/config/meeting.js b/packages/oae-jitsi/config/meeting.js index dcde64adb3..1c19926eb8 100644 --- a/packages/oae-jitsi/config/meeting.js +++ b/packages/oae-jitsi/config/meeting.js @@ -13,35 +13,33 @@ * permissions and limitations under the License. */ -var Fields = require('oae-config/lib/fields'); +import * as Fields from 'oae-config/lib/fields'; -module.exports = { - 'title': 'OAE Jitsi Module', - 'visibility': { - 'name': 'Default Visibility Value', - 'description': 'Default visibility setting for new meetings', - 'elements': { - 'meeting': new Fields.List('Meetings Visibility', 'Default visibility for a new meeting', 'public', [ - { - 'name': 'Public', - 'value': 'public' - }, - { - 'name': 'Logged in users', - 'value': 'loggedin' - }, - { - 'name': 'Private', - 'value': 'private' - } - ]) - } - }, - 'server': { - 'name': 'Jitsi Configuration', - 'description': 'Core Configuration', - 'elements': { - 'host': new Fields.Text('Jitsi server address', 'Jitsi server address', ''), - } - } +export const title = 'OAE Jitsi Module'; +export const visibility = { + name: 'Default Visibility Value', + description: 'Default visibility setting for new meetings', + elements: { + meeting: new Fields.List('Meetings Visibility', 'Default visibility for a new meeting', 'public', [ + { + name: 'Public', + value: 'public' + }, + { + name: 'Logged in users', + value: 'loggedin' + }, + { + name: 'Private', + value: 'private' + } + ]) + } +}; +export const server = { + name: 'Jitsi Configuration', + description: 'Core Configuration', + elements: { + host: new Fields.Text('Jitsi server address', 'Jitsi server address', '') + } }; diff --git a/packages/oae-jitsi/lib/activity.js b/packages/oae-jitsi/lib/activity.js index 3905c187ec..b52802bbbd 100644 --- a/packages/oae-jitsi/lib/activity.js +++ b/packages/oae-jitsi/lib/activity.js @@ -13,23 +13,23 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); - -const ActivityAPI = require('oae-activity'); -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityModel = require('oae-activity/lib/model'); -const ActivityUtil = require('oae-activity/lib/util'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzUtil = require('oae-authz/lib/util'); -const MessageBoxAPI = require('oae-messagebox'); -const MessageBoxUtil = require('oae-messagebox/lib/util'); -const PrincipalsUtil = require('oae-principals/lib/util'); -const TenantsUtil = require('oae-tenants/lib/util'); - -const MeetingsAPI = require('./api'); -const { MeetingsConstants } = require('./constants'); -const MeetingsDAO = require('./internal/dao'); +import util from 'util'; +import _ from 'underscore'; + +import * as ActivityAPI from 'oae-activity'; +import * as ActivityModel from 'oae-activity/lib/model'; +import * as ActivityUtil from 'oae-activity/lib/util'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as MessageBoxAPI from 'oae-messagebox'; +import * as MessageBoxUtil from 'oae-messagebox/lib/util'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; +import * as TenantsUtil from 'oae-tenants/lib/util'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as MeetingsDAO from './internal/dao'; +import * as MeetingsAPI from './api'; + +import { MeetingsConstants } from './constants'; /// ///////////////// // MEETING-CREATE // @@ -60,45 +60,42 @@ ActivityAPI.registerActivityType(MeetingsConstants.activity.ACTIVITY_MEETING_CRE /*! * Post a meeting-create activity when a user creates a meeting. */ -MeetingsAPI.emitter.on( - MeetingsConstants.events.CREATED_MEETING, - (ctx, meeting, memberChangeInfo) => { - const millis = Date.now(); - const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { - user: ctx.user() - }); - const objectResource = new ActivityModel.ActivitySeedResource('meeting-jitsi', meeting.id, { - 'meeting-jitsi': meeting - }); - let targetResource = null; - - // Get the extra members - const extraMembers = _.chain(memberChangeInfo.changes) - .keys() - .filter(member => { - return member !== ctx.user().id; - }) - .value(); - - // If we only added 1 extra user or group, we set the target to that entity - if (extraMembers.length === 1) { - const extraMember = _.first(extraMembers); - const targetResourceType = PrincipalsUtil.isGroup(extraMember) ? 'group' : 'user'; - targetResource = new ActivityModel.ActivitySeedResource(targetResourceType, extraMember); - } - - // Generate the activity seed and post it to the queue - const activitySeed = new ActivityModel.ActivitySeed( - MeetingsConstants.activity.ACTIVITY_MEETING_CREATE, - millis, - ActivityConstants.verbs.CREATE, - actorResource, - objectResource, - targetResource - ); - ActivityAPI.postActivity(ctx, activitySeed); +MeetingsAPI.emitter.on(MeetingsConstants.events.CREATED_MEETING, (ctx, meeting, memberChangeInfo) => { + const millis = Date.now(); + const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { + user: ctx.user() + }); + const objectResource = new ActivityModel.ActivitySeedResource('meeting-jitsi', meeting.id, { + 'meeting-jitsi': meeting + }); + let targetResource = null; + + // Get the extra members + const extraMembers = _.chain(memberChangeInfo.changes) + .keys() + .filter(member => { + return member !== ctx.user().id; + }) + .value(); + + // If we only added 1 extra user or group, we set the target to that entity + if (extraMembers.length === 1) { + const extraMember = _.first(extraMembers); + const targetResourceType = PrincipalsUtil.isGroup(extraMember) ? 'group' : 'user'; + targetResource = new ActivityModel.ActivitySeedResource(targetResourceType, extraMember); } -); + + // Generate the activity seed and post it to the queue + const activitySeed = new ActivityModel.ActivitySeed( + MeetingsConstants.activity.ACTIVITY_MEETING_CREATE, + millis, + ActivityConstants.verbs.CREATE, + actorResource, + objectResource, + targetResource + ); + ActivityAPI.postActivity(ctx, activitySeed); +}); /// /////////////////////////////////////////////////////////////////////// // MEETING-SHARE, MEETING-ADD-TO-LIBRARY and MEETING-UPDATE-MEMBER-ROLE // @@ -162,81 +159,72 @@ ActivityAPI.registerActivityType(MeetingsConstants.activity.ACTIVITY_MEETING_ADD /** * Post a meeting-share, meeting-add-to-library or meeting-update-member role activity based on meeting sharing */ -MeetingsAPI.emitter.on( - MeetingsConstants.events.UPDATED_MEETING_MEMBERS, - (ctx, meeting, memberChangeInfo, opts) => { - if (opts.invitation) { - // If this member update came from an invitation, we bypass adding activity as there is a - // dedicated activity for that - return; - } - - const addedPrincipalIds = _.pluck(memberChangeInfo.members.added, 'id'); - const updatedPrincipalIds = _.pluck(memberChangeInfo.members.updated, 'id'); +MeetingsAPI.emitter.on(MeetingsConstants.events.UPDATED_MEETING_MEMBERS, (ctx, meeting, memberChangeInfo, opts) => { + if (opts.invitation) { + // If this member update came from an invitation, we bypass adding activity as there is a + // dedicated activity for that + return; + } - const millis = Date.now(); - const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { - user: ctx.user() - }); - const meetingResource = new ActivityModel.ActivitySeedResource('meeting-jitsi', meeting.id, { - 'meeting-jitsi': meeting - }); - // For users that are newly added to the meeting, post either a share or "add to library" activity, depending on context - _.each(addedPrincipalIds, principalId => { - if (principalId === ctx.user().id) { - // Users can't "share" with themselves, they actually "add it to their library" - ActivityAPI.postActivity( - ctx, - new ActivityModel.ActivitySeed( - MeetingsConstants.activity.ACTIVITY_MEETING_ADD_TO_LIBRARY, - millis, - ActivityConstants.verbs.ADD, - actorResource, - meetingResource - ) - ); - } else { - // A user shared meeting with some other user, fire the meeting share activity - const principalResourceType = PrincipalsUtil.isGroup(principalId) ? 'group' : 'user'; - const principalResource = new ActivityModel.ActivitySeedResource( - principalResourceType, - principalId - ); - ActivityAPI.postActivity( - ctx, - new ActivityModel.ActivitySeed( - MeetingsConstants.activity.ACTIVITY_MEETING_SHARE, - millis, - ActivityConstants.verbs.SHARE, - actorResource, - meetingResource, - principalResource - ) - ); - } - }); + const addedPrincipalIds = _.pluck(memberChangeInfo.members.added, 'id'); + const updatedPrincipalIds = _.pluck(memberChangeInfo.members.updated, 'id'); - // For users whose role changed, post the meeting-update-member-role activity - _.each(updatedPrincipalIds, principalId => { - const principalResourceType = PrincipalsUtil.isGroup(principalId) ? 'group' : 'user'; - const principalResource = new ActivityModel.ActivitySeedResource( - principalResourceType, - principalId - ); + const millis = Date.now(); + const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { + user: ctx.user() + }); + const meetingResource = new ActivityModel.ActivitySeedResource('meeting-jitsi', meeting.id, { + 'meeting-jitsi': meeting + }); + // For users that are newly added to the meeting, post either a share or "add to library" activity, depending on context + _.each(addedPrincipalIds, principalId => { + if (principalId === ctx.user().id) { + // Users can't "share" with themselves, they actually "add it to their library" ActivityAPI.postActivity( ctx, new ActivityModel.ActivitySeed( - MeetingsConstants.activity.ACTIVITY_MEETING_UPDATE_MEMBER_ROLE, + MeetingsConstants.activity.ACTIVITY_MEETING_ADD_TO_LIBRARY, millis, - ActivityConstants.verbs.UPDATE, + ActivityConstants.verbs.ADD, actorResource, - principalResource, meetingResource ) ); - }); - } -); + } else { + // A user shared meeting with some other user, fire the meeting share activity + const principalResourceType = PrincipalsUtil.isGroup(principalId) ? 'group' : 'user'; + const principalResource = new ActivityModel.ActivitySeedResource(principalResourceType, principalId); + ActivityAPI.postActivity( + ctx, + new ActivityModel.ActivitySeed( + MeetingsConstants.activity.ACTIVITY_MEETING_SHARE, + millis, + ActivityConstants.verbs.SHARE, + actorResource, + meetingResource, + principalResource + ) + ); + } + }); + + // For users whose role changed, post the meeting-update-member-role activity + _.each(updatedPrincipalIds, principalId => { + const principalResourceType = PrincipalsUtil.isGroup(principalId) ? 'group' : 'user'; + const principalResource = new ActivityModel.ActivitySeedResource(principalResourceType, principalId); + ActivityAPI.postActivity( + ctx, + new ActivityModel.ActivitySeed( + MeetingsConstants.activity.ACTIVITY_MEETING_UPDATE_MEMBER_ROLE, + millis, + ActivityConstants.verbs.UPDATE, + actorResource, + principalResource, + meetingResource + ) + ); + }); +}); /// /////////////////////////////////////////////// // MEETING-UPDATE and MEETING-UPDATE-VISIBILITY // @@ -346,33 +334,29 @@ ActivityAPI.registerActivityType(MeetingsConstants.activity.ACTIVITY_MEETING_MES /** * Post a meeting-jitsi-message activity when an user comments on a meeting */ -MeetingsAPI.emitter.on( - MeetingsConstants.events.CREATED_MEETING_MESSAGE, - (ctx, message, meeting) => { - const millis = Date.now(); - const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { - user: ctx.user() - }); - const objectResource = new ActivityModel.ActivitySeedResource( - 'meeting-jitsi-message', - message.id, - { meetingId: meeting.id, message } - ); - const targetResource = new ActivityModel.ActivitySeedResource('meeting-jitsi', meeting.id, { - 'meeting-jitsi': meeting - }); - const activitySeed = new ActivityModel.ActivitySeed( - MeetingsConstants.activity.ACTIVITY_MEETING_MESSAGE, - millis, - ActivityConstants.verbs.POST, - actorResource, - objectResource, - targetResource - ); +MeetingsAPI.emitter.on(MeetingsConstants.events.CREATED_MEETING_MESSAGE, (ctx, message, meeting) => { + const millis = Date.now(); + const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { + user: ctx.user() + }); + const objectResource = new ActivityModel.ActivitySeedResource('meeting-jitsi-message', message.id, { + meetingId: meeting.id, + message + }); + const targetResource = new ActivityModel.ActivitySeedResource('meeting-jitsi', meeting.id, { + 'meeting-jitsi': meeting + }); + const activitySeed = new ActivityModel.ActivitySeed( + MeetingsConstants.activity.ACTIVITY_MEETING_MESSAGE, + millis, + ActivityConstants.verbs.POST, + actorResource, + objectResource, + targetResource + ); - ActivityAPI.postActivity(ctx, activitySeed); - } -); + ActivityAPI.postActivity(ctx, activitySeed); +}); /// //////////////////////// // ACTIVITY ENTITY TYPES // @@ -384,9 +368,7 @@ MeetingsAPI.emitter.on( */ const _meetingProducer = function(resource, callback) { const meeting = - resource.resourceData && resource.resourceData['meeting-jitsi'] - ? resource.resourceData['meeting-jitsi'] - : null; + resource.resourceData && resource.resourceData['meeting-jitsi'] ? resource.resourceData['meeting-jitsi'] : null; // If the meeting item was fired with the resource, use it instead of fetching if (meeting) { @@ -452,10 +434,7 @@ const _meetingTransformer = function(ctx, activityEntities, callback) { _.each(entities, (entity, entityId) => { // Transform the persistent entity into an ActivityStrea.ms compliant format - transformedActivityEntities[activityId][entityId] = _transformPersistentMeetingActivityEntity( - ctx, - entity - ); + transformedActivityEntities[activityId][entityId] = _transformPersistentMeetingActivityEntity(ctx, entity); }); }); @@ -474,15 +453,9 @@ const _meetingMessageTransformer = function(ctx, activityEntities, callback) { const entity = activityEntities[activityId][entityId]; const { meetingId } = entity; const resource = AuthzUtil.getResourceFromId(meetingId); - const profilePath = util.format( - '/meeting-jitsi/%s/%s', - resource.tenanAlias, - resource.resourceId - ); + const profilePath = util.format('/meeting-jitsi/%s/%s', resource.tenanAlias, resource.resourceId); const urlFormat = '/api/meeting-jitsi/' + meetingId + '/messages/%s'; - transformedActivityEntities[activityId][ - entityId - ] = MessageBoxUtil.transformPersistentMessageActivityEntity( + transformedActivityEntities[activityId][entityId] = MessageBoxUtil.transformPersistentMessageActivityEntity( ctx, entity, profilePath, @@ -592,13 +565,9 @@ ActivityAPI.registerActivityEntityType('meeting-jitsi-message', { /* * Register an association that presents the meeting */ -ActivityAPI.registerActivityEntityAssociation( - 'meeting-jitsi', - 'self', - (associationsCtx, entity, callback) => { - return callback(null, [entity[ActivityConstants.properties.OAE_ID]]); - } -); +ActivityAPI.registerActivityEntityAssociation('meeting-jitsi', 'self', (associationsCtx, entity, callback) => { + return callback(null, [entity[ActivityConstants.properties.OAE_ID]]); +}); /* * Register an association that presents the members of a meeting categorized by role @@ -614,36 +583,28 @@ ActivityAPI.registerActivityEntityAssociation( /* * Register an association that presents all the indirect members of a meeting */ -ActivityAPI.registerActivityEntityAssociation( - 'meeting-jitsi', - 'members', - (associationsCtx, entity, callback) => { - associationsCtx.get('members-by-role', (err, membersByRole) => { - if (err) { - return callback(err); - } +ActivityAPI.registerActivityEntityAssociation('meeting-jitsi', 'members', (associationsCtx, entity, callback) => { + associationsCtx.get('members-by-role', (err, membersByRole) => { + if (err) { + return callback(err); + } - return callback(null, _.flatten(_.values(membersByRole))); - }); - } -); + return callback(null, _.flatten(_.values(membersByRole))); + }); +}); /* * Register an association that presents all the managers of a meeting */ -ActivityAPI.registerActivityEntityAssociation( - 'meeting-jitsi', - 'managers', - (associationsCtx, entity, callback) => { - associationsCtx.get('members-by-role', (err, membersByRole) => { - if (err) { - return callback(err); - } +ActivityAPI.registerActivityEntityAssociation('meeting-jitsi', 'managers', (associationsCtx, entity, callback) => { + associationsCtx.get('members-by-role', (err, membersByRole) => { + if (err) { + return callback(err); + } - return callback(null, membersByRole[MeetingsConstants.roles.MANAGER]); - }); - } -); + return callback(null, membersByRole[MeetingsConstants.roles.MANAGER]); + }); +}); /* * Register an assocation that presents all the commenting contributors of a meeting @@ -652,22 +613,13 @@ ActivityAPI.registerActivityEntityAssociation( 'meeting-jitsi', 'message-contributors', (associationsCtx, entity, callback) => { - MessageBoxAPI.getRecentContributions( - entity[ActivityConstants.properties.OAE_ID], - null, - 100, - callback - ); + MessageBoxAPI.getRecentContributions(entity[ActivityConstants.properties.OAE_ID], null, 100, callback); } ); /*! * Register an association that presents the meeting for a meeting-message entity */ -ActivityAPI.registerActivityEntityAssociation( - 'meeting-jitsi-message', - 'self', - (associationsCtx, entity, callback) => { - return callback(null, [entity.meetingId]); - } -); +ActivityAPI.registerActivityEntityAssociation('meeting-jitsi-message', 'self', (associationsCtx, entity, callback) => { + return callback(null, [entity.meetingId]); +}); diff --git a/packages/oae-jitsi/lib/api.js b/packages/oae-jitsi/lib/api.js index 81a17c6e00..b90ca84fb6 100644 --- a/packages/oae-jitsi/lib/api.js +++ b/packages/oae-jitsi/lib/api.js @@ -13,11 +13,10 @@ * permissions and limitations under the License. */ -var EmitterAPI = require('oae-emitter'); +import * as EmitterAPI from 'oae-emitter'; +import * as Meetings from './api.meetings'; -var meetingsAPI = new EmitterAPI.EventEmitter(); +const meetingsAPI = new EmitterAPI.EventEmitter(); -module.exports = { - emitter: meetingsAPI -}; -module.exports.Meetings = require('./api.meetings'); +export { meetingsAPI as emitter }; +export { Meetings }; diff --git a/packages/oae-jitsi/lib/api.meetings.js b/packages/oae-jitsi/lib/api.meetings.js index 9408593cd7..35fc41f365 100644 --- a/packages/oae-jitsi/lib/api.meetings.js +++ b/packages/oae-jitsi/lib/api.meetings.js @@ -1,25 +1,28 @@ -const _ = require('underscore'); - -const AuthzAPI = require('oae-authz'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzInvitations = require('oae-authz/lib/invitations'); -const AuthzPermissions = require('oae-authz/lib/permissions'); -const LibraryAPI = require('oae-library'); -const log = require('oae-logger').logger('meetings-jitsi-api'); -const MessageBoxAPI = require('oae-messagebox'); -const { MessageBoxConstants } = require('oae-messagebox/lib/constants'); -const OaeUtil = require('oae-util/lib/util'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const PrincipalsUtil = require('oae-principals/lib/util'); -const ResourceActions = require('oae-resource/lib/actions'); -const Signature = require('oae-util/lib/signature'); -const { Validator } = require('oae-authz/lib/validator'); - -const Config = require('oae-config/lib/api').config('oae-jitsi'); -const MeetingsAPI = require('oae-jitsi'); -const { MeetingsConstants } = require('./constants'); -const MeetingsDAO = require('./internal/dao'); - +import _ from 'underscore'; + +import * as AuthzAPI from 'oae-authz'; +import * as AuthzInvitations from 'oae-authz/lib/invitations'; +import * as AuthzPermissions from 'oae-authz/lib/permissions'; +import * as LibraryAPI from 'oae-library'; +import * as MessageBoxAPI from 'oae-messagebox'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; +import * as ResourceActions from 'oae-resource/lib/actions'; +import * as Signature from 'oae-util/lib/signature'; +import { setUpConfig } from 'oae-config'; +import * as MeetingsAPI from 'oae-jitsi'; +import { logger } from 'oae-logger'; + +import { MessageBoxConstants } from 'oae-messagebox/lib/constants'; +import { Validator } from 'oae-authz/lib/validator'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { MeetingsConstants } from './constants'; +import * as MeetingsDAO from './internal/dao'; + +const Config = setUpConfig('oae-jitsi'); + +const log = logger('meetings-jitsi-api'); /** * PUBLIC FUNCTIONS */ @@ -59,27 +62,22 @@ const createMeeting = function( if (chat) { chat = String(chat) === 'true'; } + if (contactList) { contactList = String(contactList) === 'true'; } // Verify basic properties const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'Anonymous users cannot create a meeting' }) - .isLoggedInUser(ctx); - validator - .check(displayName, { code: 400, msg: 'Must provide a display name for the meeting' }) - .notEmpty(); + validator.check(null, { code: 401, msg: 'Anonymous users cannot create a meeting' }).isLoggedInUser(ctx); + validator.check(displayName, { code: 400, msg: 'Must provide a display name for the meeting' }).notEmpty(); validator .check(displayName, { code: 400, msg: 'A display name can be at most 1000 characters long' }) .isShortString(); validator .check(visibility, { code: 400, - msg: - 'An invalid meeting visibility option has been provided. Must be one of: ' + - allVisibilities.join(', ') + msg: 'An invalid meeting visibility option has been provided. Must be one of: ' + allVisibilities.join(', ') }) .isIn(allVisibilities); if (description && description.length > 0) { @@ -120,19 +118,13 @@ const createMeeting = function( return callback(err); } - MeetingsAPI.emitter.emit( - MeetingsConstants.events.CREATED_MEETING, - ctx, - meeting, - memberChangeInfo, - errs => { - if (errs) { - return callback(_.first(errs)); - } - - return callback(null, meeting); + MeetingsAPI.emitter.emit(MeetingsConstants.events.CREATED_MEETING, ctx, meeting, memberChangeInfo, errs => { + if (errs) { + return callback(_.first(errs)); } - ); + + return callback(null, meeting); + }); }); }; @@ -147,9 +139,7 @@ const createMeeting = function( */ const getFullMeetingProfile = function(ctx, meetingId, callback) { const validator = new Validator(); - validator - .check(meetingId, { code: 400, msg: 'meetingId must be a valid resource id' }) - .isResourceId(); + validator.check(meetingId, { code: 400, msg: 'meetingId must be a valid resource id' }).isResourceId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -164,6 +154,7 @@ const getFullMeetingProfile = function(ctx, meetingId, callback) { if (err) { return callback(err); } + if (!permissions.canView) { // The user has no effective role, which means they are not allowed to view (this has already taken into // consideration implicit privacy rules, such as whether or not the meeting is public). @@ -212,9 +203,7 @@ const getFullMeetingProfile = function(ctx, meetingId, callback) { */ const getMeeting = function(ctx, meetingId, callback) { const validator = new Validator(); - validator - .check(meetingId, { code: 400, msg: 'A valid resource id must be specified' }) - .isResourceId(); + validator.check(meetingId, { code: 400, msg: 'A valid resource id must be specified' }).isResourceId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -243,9 +232,7 @@ const getMeeting = function(ctx, meetingId, callback) { */ const getMeetingInvitations = function(ctx, meetingId, callback) { const validator = new Validator(); - validator - .check(meetingId, { code: 400, msg: 'A valid resource id must be specified' }) - .isResourceId(); + validator.check(meetingId, { code: 400, msg: 'A valid resource id must be specified' }).isResourceId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -270,9 +257,7 @@ const getMeetingMembers = function(ctx, meetingId, start, limit, callback) { limit = OaeUtil.getNumberParam(limit, 10, 1); const validator = new Validator(); - validator - .check(meetingId, { code: 400, msg: 'A valid resource id must be specified' }) - .isResourceId(); + validator.check(meetingId, { code: 400, msg: 'A valid resource id must be specified' }).isResourceId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -329,6 +314,7 @@ const updateMeeting = function(ctx, meetingId, profileFields, callback) { profileFields.chat = false; } } + if (profileFields.contactList) { if (profileFields.contactList === 'true') { profileFields.contactList = true; @@ -338,12 +324,8 @@ const updateMeeting = function(ctx, meetingId, profileFields, callback) { } const validator = new Validator(); - validator - .check(meetingId, { code: 400, msg: 'A valid resource id must be specified' }) - .isResourceId(); - validator - .check(null, { code: 401, msg: 'You must be authenticated to update a meeting' }) - .isLoggedInUser(ctx); + validator.check(meetingId, { code: 400, msg: 'A valid resource id must be specified' }).isResourceId(); + validator.check(null, { code: 401, msg: 'You must be authenticated to update a meeting' }).isLoggedInUser(ctx); validator .check(_.keys(profileFields).length, { code: 400, @@ -355,10 +337,7 @@ const updateMeeting = function(ctx, meetingId, profileFields, callback) { .check(field, { code: 400, msg: - "The field '" + - field + - "' is not a valid field. Must be one of: " + - MeetingsConstants.updateFields.join(', ') + "The field '" + field + "' is not a valid field. Must be one of: " + MeetingsConstants.updateFields.join(', ') }) .isIn(MeetingsConstants.updateFields); if (field === 'visibility') { @@ -370,13 +349,9 @@ const updateMeeting = function(ctx, meetingId, profileFields, callback) { .isIn(allVisibilities); } else if (field === 'displayName') { validator.check(value, { code: 400, msg: 'A display name cannot be empty' }).notEmpty(); - validator - .check(value, { code: 400, msg: 'A display name can be at most 1000 characters long' }) - .isShortString(); + validator.check(value, { code: 400, msg: 'A display name can be at most 1000 characters long' }).isShortString(); } else if (field === 'description' && value.length > 0) { - validator - .check(value, { code: 400, msg: 'A description can be at most 10000 characters long' }) - .isMediumString(); + validator.check(value, { code: 400, msg: 'A description can be at most 10000 characters long' }).isMediumString(); } else if (field === 'chat') { validator .check(null, { code: 400, msg: 'An invalid chat value was specified, must be boolean' }) @@ -415,19 +390,13 @@ const updateMeeting = function(ctx, meetingId, profileFields, callback) { updatedMeeting.canPost = true; updatedMeeting.canShare = true; - MeetingsAPI.emitter.emit( - MeetingsConstants.events.UPDATED_MEETING, - ctx, - updatedMeeting, - meeting, - errs => { - if (errs) { - return callback(_.first(errs)); - } - - return callback(null, updatedMeeting); + MeetingsAPI.emitter.emit(MeetingsConstants.events.UPDATED_MEETING, ctx, updatedMeeting, meeting, errs => { + if (errs) { + return callback(_.first(errs)); } - ); + + return callback(null, updatedMeeting); + }); }); }); }); @@ -443,12 +412,8 @@ const updateMeeting = function(ctx, meetingId, profileFields, callback) { */ const deleteMeeting = function(ctx, meetingId, callback) { const validator = new Validator(); - validator - .check(meetingId, { code: 400, msg: 'A valid resource id must be specified' }) - .isResourceId(); - validator - .check(null, { code: 401, msg: 'You must be authenticated to delete a meeting' }) - .isLoggedInUser(ctx); + validator.check(meetingId, { code: 400, msg: 'A valid resource id must be specified' }).isResourceId(); + validator.check(null, { code: 401, msg: 'You must be authenticated to delete a meeting' }).isLoggedInUser(ctx); if (validator.hasErrors()) { return callback(validator.getFirstError()); @@ -487,19 +452,13 @@ const deleteMeeting = function(ctx, meetingId, callback) { return callback(err); } - MeetingsAPI.emitter.emit( - MeetingsConstants.events.DELETED_MEETING, - ctx, - meeting, - memberIds, - errs => { - if (errs) { - return callback(_.first(errs)); - } - - return callback(); + MeetingsAPI.emitter.emit(MeetingsConstants.events.DELETED_MEETING, ctx, meeting, memberIds, errs => { + if (errs) { + return callback(_.first(errs)); } - ); + + return callback(); + }); }); }); }); @@ -518,19 +477,14 @@ const deleteMeeting = function(ctx, meetingId, callback) { */ const setMeetingMembers = function(ctx, meetingId, changes, callback) { const validator = new Validator(); - validator - .check(meetingId, { code: 400, msg: 'A valid resource id must be specified' }) - .isResourceId(); - validator - .check(null, { code: 401, msg: 'You must be authenticated to update meeting members' }) - .isLoggedInUser(ctx); + validator.check(meetingId, { code: 400, msg: 'A valid resource id must be specified' }).isResourceId(); + validator.check(null, { code: 401, msg: 'You must be authenticated to update meeting members' }).isLoggedInUser(ctx); // eslint-disable-next-line no-unused-vars _.each(changes, (role, principalId) => { validator .check(role, { code: 400, - msg: - 'The role change : ' + role + ' is not a valid value. Must either be a string, or false' + msg: 'The role change : ' + role + ' is not a valid value. Must either be a string, or false' }) .isValidRoleChange(); if (role) { @@ -610,40 +564,34 @@ const getMessages = function(ctx, meetingId, start, limit, callback) { } // Fetch the messages from the message box - MessageBoxAPI.getMessagesFromMessageBox( - meetingId, - start, - limit, - null, - (err, messages, nextToken) => { + MessageBoxAPI.getMessagesFromMessageBox(meetingId, start, limit, null, (err, messages, nextToken) => { + if (err) { + return callback(err); + } + + let userIds = _.map(messages, message => { + return message.createdBy; + }); + + // Remove falsey and duplicate userIds + userIds = _.uniq(_.compact(userIds)); + + // Get the basic principal profiles of the messagers to add to the messages as `createdBy`. + PrincipalsUtil.getPrincipals(ctx, userIds, (err, users) => { if (err) { return callback(err); } - let userIds = _.map(messages, message => { - return message.createdBy; - }); - - // Remove falsey and duplicate userIds - userIds = _.uniq(_.compact(userIds)); - - // Get the basic principal profiles of the messagers to add to the messages as `createdBy`. - PrincipalsUtil.getPrincipals(ctx, userIds, (err, users) => { - if (err) { - return callback(err); + // Attach the user profiles to the message objects + _.each(messages, message => { + if (users[message.createdBy]) { + message.createdBy = users[message.createdBy]; } - - // Attach the user profiles to the message objects - _.each(messages, message => { - if (users[message.createdBy]) { - message.createdBy = users[message.createdBy]; - } - }); - - return callback(err, messages, nextToken); }); - } - ); + + return callback(err, messages, nextToken); + }); + }); }); }; @@ -661,18 +609,12 @@ const getMessages = function(ctx, meetingId, start, limit, callback) { */ const createMessage = function(ctx, meetingId, body, replyToCreatedTimestamp, callback) { const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'Only authenticated users can post on meetings' }) - .isLoggedInUser(ctx); + validator.check(null, { code: 401, msg: 'Only authenticated users can post on meetings' }).isLoggedInUser(ctx); validator.check(meetingId, { code: 400, msg: 'Invalid meeting id provided' }).isResourceId(); validator.check(body, { code: 400, msg: 'A message body must be provided' }).notEmpty(); - validator - .check(body, { code: 400, msg: 'A message body can only be 100000 characters long' }) - .isLongString(); + validator.check(body, { code: 400, msg: 'A message body can only be 100000 characters long' }).isLongString(); if (replyToCreatedTimestamp) { - validator - .check(replyToCreatedTimestamp, { code: 400, msg: 'Invalid reply-to timestamp provided' }) - .isInt(); + validator.check(replyToCreatedTimestamp, { code: 400, msg: 'Invalid reply-to timestamp provided' }).isInt(); } if (validator.hasErrors()) { @@ -711,19 +653,13 @@ const createMessage = function(ctx, meetingId, body, replyToCreatedTimestamp, ca message.createdBy = createdBy; // The message has been created in the database so we can emit the `created-message` event - MeetingsAPI.emitter.emit( - MeetingsConstants.events.CREATED_MEETING_MESSAGE, - ctx, - message, - meeting, - errs => { - if (errs) { - return callback(_.first(errs)); - } - - return callback(null, message); + MeetingsAPI.emitter.emit(MeetingsConstants.events.CREATED_MEETING_MESSAGE, ctx, message, meeting, errs => { + if (errs) { + return callback(_.first(errs)); } - ); + + return callback(null, message); + }); }); } ); @@ -744,9 +680,7 @@ const createMessage = function(ctx, meetingId, body, replyToCreatedTimestamp, ca */ const deleteMessage = function(ctx, meetingId, messageCreatedDate, callback) { const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'Only authenticated users can delete messages' }) - .isLoggedInUser(ctx); + validator.check(null, { code: 401, msg: 'Only authenticated users can delete messages' }).isLoggedInUser(ctx); validator.check(meetingId, { code: 400, msg: 'A meeting id must be provided' }).isResourceId(); validator .check(messageCreatedDate, { @@ -765,54 +699,51 @@ const deleteMessage = function(ctx, meetingId, messageCreatedDate, callback) { } // Ensure that the message exists. We also need it so we can make sure we have access to delete it - MessageBoxAPI.getMessages( - meetingId, - [messageCreatedDate], - { scrubDeleted: false }, - (err, messages) => { + MessageBoxAPI.getMessages(meetingId, [messageCreatedDate], { scrubDeleted: false }, (err, messages) => { + if (err) { + return callback(err); + } + + if (!messages[0]) { + return callback({ code: 404, msg: 'The specified message does not exist' }); + } + + const message = messages[0]; + + // Determine if we have access to delete the meeting message + AuthzPermissions.canManageMessage(ctx, meeting, message, err => { if (err) { return callback(err); } - if (!messages[0]) { - return callback({ code: 404, msg: 'The specified message does not exist' }); - } - - const message = messages[0]; - // Determine if we have access to delete the meeting message - AuthzPermissions.canManageMessage(ctx, meeting, message, err => { - if (err) { - return callback(err); - } + // Delete the message using the "leaf" method, which will SOFT delete if the message has replies, or HARD delete if it does not + MessageBoxAPI.deleteMessage( + meetingId, + messageCreatedDate, + { deleteType: MessageBoxConstants.deleteTypes.LEAF }, + (err, deleteType, deletedMessage) => { + if (err) { + return callback(err); + } - // Delete the message using the "leaf" method, which will SOFT delete if the message has replies, or HARD delete if it does not - MessageBoxAPI.deleteMessage( - meetingId, - messageCreatedDate, - { deleteType: MessageBoxConstants.deleteTypes.LEAF }, - (err, deleteType, deletedMessage) => { - if (err) { - return callback(err); - } + MeetingsAPI.emitter.emit( + MeetingsConstants.events.DELETED_MEETING_MESSAGE, + ctx, + message, + meeting, + deleteType + ); - MeetingsAPI.emitter.emit( - MeetingsConstants.events.DELETED_MEETING_MESSAGE, - ctx, - message, - meeting, - deleteType - ); - - // If a soft-delete occurred, we want to inform the consumer of the soft-delete message model - if (deleteType === MessageBoxConstants.deleteTypes.SOFT) { - return callback(null, deletedMessage); - } - return callback(); + // If a soft-delete occurred, we want to inform the consumer of the soft-delete message model + if (deleteType === MessageBoxConstants.deleteTypes.SOFT) { + return callback(null, deletedMessage); } - ); - }); - } - ); + + return callback(); + } + ); + }); + }); }); }; @@ -833,9 +764,7 @@ const getMeetingsLibrary = function(ctx, principalId, start, limit, callback) { limit = OaeUtil.getNumberParam(limit, 10, 1); const validator = new Validator(); - validator - .check(principalId, { code: 400, msg: 'A user or group id must be provided' }) - .isPrincipalId(); + validator.check(principalId, { code: 400, msg: 'A user or group id must be provided' }).isPrincipalId(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -847,53 +776,49 @@ const getMeetingsLibrary = function(ctx, principalId, start, limit, callback) { } // Determine which library visibility the current user should receive - LibraryAPI.Authz.resolveTargetLibraryAccess( - ctx, - principal.id, - principal, - (err, hasAccess, visibility) => { - if (err) { - return callback(err); - } - if (!hasAccess) { - return callback({ code: 401, msg: 'You do not have have access to this library' }); - } + LibraryAPI.Authz.resolveTargetLibraryAccess(ctx, principal.id, principal, (err, hasAccess, visibility) => { + if (err) { + return callback(err); + } + + if (!hasAccess) { + return callback({ code: 401, msg: 'You do not have have access to this library' }); + } - // Get the meeting ids from the library index - LibraryAPI.Index.list( - MeetingsConstants.library.MEETINGS_LIBRARY_INDEX_NAME, - principalId, - visibility, - { start, limit }, - (err, entries, nextToken) => { + // Get the meeting ids from the library index + LibraryAPI.Index.list( + MeetingsConstants.library.MEETINGS_LIBRARY_INDEX_NAME, + principalId, + visibility, + { start, limit }, + (err, entries, nextToken) => { + if (err) { + return callback(err); + } + + // Get the meeting objects from the meeting ids + const meetingIds = _.pluck(entries, 'resourceId'); + MeetingsDAO.getMeetingsById(meetingIds, (err, meetings) => { if (err) { return callback(err); } - // Get the meeting objects from the meeting ids - const meetingIds = _.pluck(entries, 'resourceId'); - MeetingsDAO.getMeetingsById(meetingIds, (err, meetings) => { - if (err) { - return callback(err); - } + // Emit an event indicating that the meeting library has been retrieved + MeetingsAPI.emitter.emit( + MeetingsConstants.events.GET_MEETING_LIBRARY, + ctx, + principalId, + visibility, + start, + limit, + meetings + ); - // Emit an event indicating that the meeting library has been retrieved - MeetingsAPI.emitter.emit( - MeetingsConstants.events.GET_MEETING_LIBRARY, - ctx, - principalId, - visibility, - start, - limit, - meetings - ); - - return callback(null, meetings, nextToken); - }); - } - ); - } - ); + return callback(null, meetings, nextToken); + }); + } + ); + }); }); }; @@ -914,9 +839,7 @@ const removeMeetingFromLibrary = function(ctx, libraryOwnerId, meetingId, callba validator .check(null, { code: 401, msg: 'You must be authenticated to remove a meeting from a library' }) .isLoggedInUser(ctx); - validator - .check(libraryOwnerId, { code: 400, msg: 'An user or group id must be provided' }) - .isPrincipalId(); + validator.check(libraryOwnerId, { code: 400, msg: 'An user or group id must be provided' }).isPrincipalId(); validator .check(meetingId, { code: 400, msg: 'An invalid meeting id "' + meetingId + '" was provided' }) .isResourceId(); @@ -984,6 +907,7 @@ const _getMeeting = function(meetingId, callback) { if (err) { return callback(err); } + if (!meeting) { return callback({ code: 404, msg: 'Could not find meeting : ' + meetingId }); } @@ -992,7 +916,7 @@ const _getMeeting = function(meetingId, callback) { }); }; -module.exports = { +export { createMeeting, getFullMeetingProfile, getMeetingInvitations, diff --git a/packages/oae-jitsi/lib/constants.js b/packages/oae-jitsi/lib/constants.js index 847ad2357a..ca44d6c4d1 100644 --- a/packages/oae-jitsi/lib/constants.js +++ b/packages/oae-jitsi/lib/constants.js @@ -45,13 +45,7 @@ MeetingsConstants.activity = { ACTIVITY_MEETING_MESSAGE: 'meeting-jitsi-message' }; -MeetingsConstants.updateFields = [ - 'displayName', - 'description', - 'chat', - 'contactList', - 'visibility' -]; +MeetingsConstants.updateFields = ['displayName', 'description', 'chat', 'contactList', 'visibility']; MeetingsConstants.library = { MEETINGS_LIBRARY_INDEX_NAME: 'meetings-jitsi:meetings-jitsi' @@ -61,4 +55,4 @@ MeetingsConstants.search = { MAPPING_MEETING_MESSAGE: 'meeting-jitsi_message' }; -module.exports = { MeetingsConstants }; +export { MeetingsConstants }; diff --git a/packages/oae-jitsi/lib/init.js b/packages/oae-jitsi/lib/init.js index edf09f8ae0..bc559b3994 100644 --- a/packages/oae-jitsi/lib/init.js +++ b/packages/oae-jitsi/lib/init.js @@ -13,11 +13,13 @@ * permissions and limitations under the License. */ -const log = require('oae-logger').logger('oae-jitsi-init'); +import { logger } from 'oae-logger'; -const MeetingSearch = require('./search'); +import * as MeetingSearch from './search'; -module.exports = function(config, callback) { +const log = logger('oae-jitsi-init'); + +export function init(config, callback) { log().info('Initializing the oae-jitsi module'); // Register the activity functionality @@ -29,4 +31,4 @@ module.exports = function(config, callback) { const library = require('./library'); return MeetingSearch.init(callback); -}; +} diff --git a/packages/oae-jitsi/lib/internal/dao.js b/packages/oae-jitsi/lib/internal/dao.js index e7ba1d380a..e087be121a 100644 --- a/packages/oae-jitsi/lib/internal/dao.js +++ b/packages/oae-jitsi/lib/internal/dao.js @@ -1,12 +1,11 @@ -const _ = require('underscore'); -const ShortId = require('shortid'); +import _ from 'underscore'; +import ShortId from 'shortid'; +import { Meeting } from 'oae-jitsi/lib/model'; -const AuthzUtil = require('oae-authz/lib/util'); -const Cassandra = require('oae-util/lib/cassandra'); -const OaeUtil = require('oae-util/lib/util'); -const TenantsAPI = require('oae-tenants'); - -const { Meeting } = require('oae-jitsi/lib/model'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as TenantsAPI from 'oae-tenants'; /** * PUBLIC FUNCTIONS @@ -15,15 +14,7 @@ const { Meeting } = require('oae-jitsi/lib/model'); /** * Create a new meeting. */ -const createMeeting = function( - createdBy, - displayName, - description, - chat, - contactList, - visibility, - callback -) { +const createMeeting = function(createdBy, displayName, description, chat, contactList, visibility, callback) { const created = Date.now().toString(); const { tenantAlias } = AuthzUtil.getPrincipalFromId(createdBy); @@ -160,23 +151,16 @@ const iterateAll = function(properties, batchSize, onEach, callback) { } /* - * Handles each batch from the cassandra iterateAll method - * - * @see Cassandra#iterateAll - */ + * Handles each batch from the cassandra iterateAll method + * + * @see Cassandra#iterateAll + */ const _iterateAllOnEach = function(rows, done) { // Convert the rows to a hash and delegate action to the caller onEach method return onEach(_.map(rows, Cassandra.rowToHash), done); }; - Cassandra.iterateAll( - properties, - 'MeetingsJitsi', - 'id', - { batchSize }, - _iterateAllOnEach, - callback - ); + Cassandra.iterateAll(properties, 'MeetingsJitsi', 'id', { batchSize }, _iterateAllOnEach, callback); }; /** @@ -262,11 +246,4 @@ const _createUpdatedMeetingFromStorageHash = function(meeting, hash) { ); }; -module.exports = { - createMeeting, - getMeeting, - getMeetingsById, - updateMeeting, - deleteMeeting, - iterateAll -}; +export { createMeeting, getMeeting, getMeetingsById, updateMeeting, deleteMeeting, iterateAll }; diff --git a/packages/oae-jitsi/lib/library.js b/packages/oae-jitsi/lib/library.js index 558d181b26..02aae8c9e3 100644 --- a/packages/oae-jitsi/lib/library.js +++ b/packages/oae-jitsi/lib/library.js @@ -13,15 +13,18 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const AuthzAPI = require('oae-authz'); -const LibraryAPI = require('oae-library'); -const log = require('oae-logger').logger('meetings-jitsi-library'); +import { logger } from 'oae-logger'; -const MeetingsAPI = require('oae-jitsi'); -const { MeetingsConstants } = require('oae-jitsi/lib/constants'); -const MeetingsDAO = require('oae-jitsi/lib/internal/dao'); +import * as AuthzAPI from 'oae-authz'; +import * as LibraryAPI from 'oae-library'; +import * as MeetingsAPI from 'oae-jitsi'; +import * as MeetingsDAO from 'oae-jitsi/lib/internal/dao'; + +import { MeetingsConstants } from 'oae-jitsi/lib/constants'; + +const log = logger('meetings-jitsi-library'); /** * Register a library indexer that can provide resources to reindex the meeting library @@ -29,35 +32,29 @@ const MeetingsDAO = require('oae-jitsi/lib/internal/dao'); LibraryAPI.Index.registerLibraryIndex(MeetingsConstants.library.MEETINGS_LIBRARY_INDEX_NAME, { pageResources(libraryId, start, limit, callback) { // Query all the meeting ids ('m') to which the library owner is directly associated - AuthzAPI.getRolesForPrincipalAndResourceType( - libraryId, - 'm', - start, - limit, - (err, roles, nextToken) => { + AuthzAPI.getRolesForPrincipalAndResourceType(libraryId, 'm', start, limit, (err, roles, nextToken) => { + if (err) { + return callback(err); + } + + const ids = _.pluck(roles, 'id'); + + MeetingsDAO.getMeetingsById(ids, (err, meetings) => { if (err) { return callback(err); } - const ids = _.pluck(roles, 'id'); - - MeetingsDAO.getMeetingsById(ids, (err, meetings) => { - if (err) { - return callback(err); - } - - // Convert all the meetings into the light-weight library items that describe how its placed in a library index - const resources = _.chain(meetings) - .compact() - .map(meeting => { - return { rank: meeting.lastModified, resource: meeting }; - }) - .value(); + // Convert all the meetings into the light-weight library items that describe how its placed in a library index + const resources = _.chain(meetings) + .compact() + .map(meeting => { + return { rank: meeting.lastModified, resource: meeting }; + }) + .value(); - return callback(null, resources, nextToken); - }); - } - ); + return callback(null, resources, nextToken); + }); + }); } }); @@ -69,68 +66,59 @@ LibraryAPI.Search.registerLibrarySearch('meeting-jitsi-library', ['meeting-jitsi /** * When a meeting is created, add the meeting to the member meeting library */ -MeetingsAPI.emitter.when( - MeetingsConstants.events.CREATED_MEETING, - (ctx, meeting, memberChangeInfo, callback) => { - const addedMemberIds = _.pluck(memberChangeInfo.members.added, 'id'); - _insertLibrary(addedMemberIds, meeting, err => { - if (err) { - log().warn( - { - err, - meetingId: meeting.id, - memberIds: addedMemberIds - }, - 'An error occurred inserting meeting into meeting libraries after create' - ); - } +MeetingsAPI.emitter.when(MeetingsConstants.events.CREATED_MEETING, (ctx, meeting, memberChangeInfo, callback) => { + const addedMemberIds = _.pluck(memberChangeInfo.members.added, 'id'); + _insertLibrary(addedMemberIds, meeting, err => { + if (err) { + log().warn( + { + err, + meetingId: meeting.id, + memberIds: addedMemberIds + }, + 'An error occurred inserting meeting into meeting libraries after create' + ); + } - return callback(); - }); - } -); + return callback(); + }); +}); /** * When a meeting is updated, update all meeting libraries with its updated last modified */ -MeetingsAPI.emitter.on( - MeetingsConstants.events.UPDATED_MEETING, - (ctx, updatedMeeting, oldMeeting) => { - // Get all the member ids of the updated meeting - _getAllMemberIds(updatedMeeting.id, (err, memberIds) => { - if (err) { - return err; - } +MeetingsAPI.emitter.on(MeetingsConstants.events.UPDATED_MEETING, (ctx, updatedMeeting, oldMeeting) => { + // Get all the member ids of the updated meeting + _getAllMemberIds(updatedMeeting.id, (err, memberIds) => { + if (err) { + return err; + } - // Perform the libraries updates - return _updateLibrary(memberIds, updatedMeeting, oldMeeting.lastModified); - }); - } -); + // Perform the libraries updates + return _updateLibrary(memberIds, updatedMeeting, oldMeeting.lastModified); + }); +}); /** * When a meeting is deleted, remove it from all the meeting libraries */ -MeetingsAPI.emitter.when( - MeetingsConstants.events.DELETED_MEETING, - (ctx, meeting, removedMemberIds, callback) => { - // Remove the meeting from all libraries - _removeFromLibrary(removedMemberIds, meeting, err => { - if (err) { - log().warn( - { - err, - meetingId: meeting.id, - memberIds: removedMemberIds - }, - 'An error occurred while removing a deleted meeting from all meeting libraries' - ); - } +MeetingsAPI.emitter.when(MeetingsConstants.events.DELETED_MEETING, (ctx, meeting, removedMemberIds, callback) => { + // Remove the meeting from all libraries + _removeFromLibrary(removedMemberIds, meeting, err => { + if (err) { + log().warn( + { + err, + meetingId: meeting.id, + memberIds: removedMemberIds + }, + 'An error occurred while removing a deleted meeting from all meeting libraries' + ); + } - return callback(); - }); - } -); + return callback(); + }); +}); /** * When meeting members are updated, pass the required updated to its members library diff --git a/packages/oae-jitsi/lib/migration.js b/packages/oae-jitsi/lib/migration.js index aec4c53b25..b48ea1d0a8 100644 --- a/packages/oae-jitsi/lib/migration.js +++ b/packages/oae-jitsi/lib/migration.js @@ -1,4 +1,4 @@ -const Cassandra = require('oae-util/lib/cassandra'); +import { createColumnFamilies } from 'oae-util/lib/cassandra'; /** * Ensure that all of the meeting-related schemas are created. If they already exist, this method will not do anything. @@ -8,7 +8,7 @@ const Cassandra = require('oae-util/lib/cassandra'); * @api private */ const ensureSchema = function(callback) { - Cassandra.createColumnFamilies( + createColumnFamilies( { MeetingsJitsi: 'CREATE TABLE "MeetingsJitsi" ("id" text PRIMARY KEY, "tenantAlias" text, "displayName" text, "visibility" text, "description" text, "createdBy" text, "created" text, "lastModified" text, "chat" boolean, "contactList" boolean)' @@ -17,4 +17,4 @@ const ensureSchema = function(callback) { ); }; -module.exports = { ensureSchema }; +export { ensureSchema }; diff --git a/packages/oae-jitsi/lib/model.js b/packages/oae-jitsi/lib/model.js index 1a7ee7a500..78e5de27d5 100644 --- a/packages/oae-jitsi/lib/model.js +++ b/packages/oae-jitsi/lib/model.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const util = require('util'); +import util from 'util'; -const AuthzUtil = require('oae-authz/lib/util'); +import * as AuthzUtil from 'oae-authz/lib/util'; const Meeting = function( tenant, @@ -47,6 +47,4 @@ const Meeting = function( return that; }; -module.exports = { - Meeting -}; +export { Meeting }; diff --git a/packages/oae-jitsi/lib/rest.js b/packages/oae-jitsi/lib/rest.js index fbef12469f..5631971fd4 100644 --- a/packages/oae-jitsi/lib/rest.js +++ b/packages/oae-jitsi/lib/rest.js @@ -13,13 +13,12 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const { AuthzConstants } = require('oae-authz/lib/constants'); -const OAE = require('oae-util/lib/oae'); -const OaeUtil = require('oae-util/lib/util'); - -const MeetingsAPI = require('oae-jitsi'); +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as OAE from 'oae-util/lib/oae'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as MeetingsAPI from 'oae-jitsi'; /** * @REST postMeetingCreate @@ -73,6 +72,7 @@ OAE.tenantRouter.on('post', '/api/meeting-jitsi/create', (req, res) => { if (err) { return res.status(err.code).send(err.msg); } + return res.status(201).send(meeting); } ); @@ -320,19 +320,13 @@ OAE.tenantRouter.on('get', '/api/meeting-jitsi/:meetingId/messages', (req, res) * @HttpResponse 404 Could not find the specified meeting */ OAE.tenantRouter.on('post', '/api/meeting-jitsi/:meetingId/messages', (req, res) => { - MeetingsAPI.Meetings.createMessage( - req.ctx, - req.params.meetingId, - req.body.body, - req.body.replyTo, - (err, message) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - res.status(200).send(message); + MeetingsAPI.Meetings.createMessage(req.ctx, req.params.meetingId, req.body.body, req.body.replyTo, (err, message) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(200).send(message); + }); }); /** @@ -356,18 +350,13 @@ OAE.tenantRouter.on('post', '/api/meeting-jitsi/:meetingId/messages', (req, res) * @HttpResponse 404 Could not find the specified message */ OAE.tenantRouter.on('delete', '/api/meeting-jitsi/:meetingId/messages/:created', (req, res) => { - MeetingsAPI.Meetings.deleteMessage( - req.ctx, - req.params.meetingId, - req.params.created, - (err, message) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - res.status(200).send(message); + MeetingsAPI.Meetings.deleteMessage(req.ctx, req.params.meetingId, req.params.created, (err, message) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + res.status(200).send(message); + }); }); /** @@ -422,16 +411,11 @@ OAE.tenantRouter.on('get', '/api/meeting-jitsi/library/:principalId', (req, res) * @HttpResponse 404 Could not find the specified meeting */ OAE.tenantRouter.on('delete', '/api/meeting-jitsi/library/:principalId/:meetingId', (req, res) => { - MeetingsAPI.Meetings.removeMeetingFromLibrary( - req.ctx, - req.params.principalId, - req.params.meetingId, - err => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(200).end(); + MeetingsAPI.Meetings.removeMeetingFromLibrary(req.ctx, req.params.principalId, req.params.meetingId, err => { + if (err) { + return res.status(err.code).send(err.msg); } - ); + + return res.status(200).end(); + }); }); diff --git a/packages/oae-jitsi/lib/search.js b/packages/oae-jitsi/lib/search.js index 661fed509c..ce69afd4b4 100644 --- a/packages/oae-jitsi/lib/search.js +++ b/packages/oae-jitsi/lib/search.js @@ -13,18 +13,20 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; +import { logger } from 'oae-logger'; -const AuthzUtil = require('oae-authz/lib/util'); -const log = require('oae-logger').logger('meeting-jitsi-search'); -const MessageBoxSearch = require('oae-messagebox/lib/search'); -const SearchAPI = require('oae-search'); -const TenantsAPI = require('oae-tenants'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as MessageBoxSearch from 'oae-messagebox/lib/search'; +import * as SearchAPI from 'oae-search'; +import * as TenantsAPI from 'oae-tenants'; +import * as MeetingsAPI from 'oae-jitsi'; +import * as MeetingsDAO from 'oae-jitsi/lib/internal/dao'; -const MeetingsAPI = require('oae-jitsi'); -const { MeetingsConstants } = require('oae-jitsi/lib/constants'); -const MeetingsDAO = require('oae-jitsi/lib/internal/dao'); +import { MeetingsConstants } from 'oae-jitsi/lib/constants'; + +const log = logger('meeting-jitsi-search'); /** * Initializes the child search documents for the meeting module @@ -99,6 +101,7 @@ const _produceMeetingSearchDocuments = function(resources, callback) { if (err) { return callback([err]); } + if (_.isEmpty(meetings)) { return callback(); } @@ -284,21 +287,18 @@ MeetingsAPI.emitter.on(MeetingsConstants.events.DELETED_MEETING, (ctx, meeting) /** * When a message is added to a meeting, we must index the child message document */ -MeetingsAPI.emitter.on( - MeetingsConstants.events.CREATED_MEETING_MESSAGE, - (ctx, message, meeting) => { - const resource = { - id: meeting.id, - messages: [message] - }; - - SearchAPI.postIndexTask('meeting-jitsi', [resource], { - children: { - 'meeting-jitsi_message': true - } - }); - } -); +MeetingsAPI.emitter.on(MeetingsConstants.events.CREATED_MEETING_MESSAGE, (ctx, message, meeting) => { + const resource = { + id: meeting.id, + messages: [message] + }; + + SearchAPI.postIndexTask('meeting-jitsi', [resource], { + children: { + 'meeting-jitsi_message': true + } + }); +}); /** * When a meeting message is deleted, we must delete the child message document @@ -322,12 +322,12 @@ MeetingsAPI.emitter.on( SearchAPI.registerReindexAllHandler('meeting-jitsi', callback => { /* - * Handles each iteration of the MeetingDAO iterate all method, firing tasks for all meetings to - * be reindexed. - * - * @see MeetingDAO#iterateAll - * @api private - */ + * Handles each iteration of the MeetingDAO iterate all method, firing tasks for all meetings to + * be reindexed. + * + * @see MeetingDAO#iterateAll + * @api private + */ const _onEach = function(meetingRows, done) { // Batch up this iteration of task resources const meetingResources = []; @@ -346,6 +346,4 @@ SearchAPI.registerReindexAllHandler('meeting-jitsi', callback => { MeetingsDAO.iterateAll(['id'], 100, _onEach, callback); }); -module.exports = { - init -}; +export { init }; diff --git a/packages/oae-jitsi/tests/test-activity.js b/packages/oae-jitsi/tests/test-activity.js index 4a11822ff9..60cbaad4a4 100644 --- a/packages/oae-jitsi/tests/test-activity.js +++ b/packages/oae-jitsi/tests/test-activity.js @@ -1,10 +1,10 @@ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); -const EmailTestsUtil = require('oae-email/lib/test/util'); -const ActivityTestsUtil = require('oae-activity/lib/test/util'); +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as EmailTestsUtil from 'oae-email/lib/test/util'; +import * as ActivityTestsUtil from 'oae-activity/lib/test/util'; describe('Meeting Activity', () => { // Rest contexts that can be used performing rest requests @@ -204,10 +204,7 @@ describe('Meeting Activity', () => { // Verify the meeting-jitsi-share activity const activity = activityStream.items[0]; assert.ok(activity); - assert.strictEqual( - activity['oae:activityType'], - 'meeting-jitsi-update-member-role' - ); + assert.strictEqual(activity['oae:activityType'], 'meeting-jitsi-update-member-role'); assert.strictEqual(activity.actor['oae:id'], simon.user.id); assert.strictEqual(activity.object['oae:id'], nico.user.id); assert.strictEqual(activity.target['oae:id'], meeting.id); @@ -263,10 +260,7 @@ describe('Meeting Activity', () => { assert.ok(activity); assert.strictEqual(activity['oae:activityType'], 'meeting-jitsi-message'); assert.strictEqual(activity.actor['oae:id'], simon.user.id); - assert.strictEqual( - activity.object['oae:id'], - meeting.id + '#' + activity.object.published - ); + assert.strictEqual(activity.object['oae:id'], meeting.id + '#' + activity.object.published); return callback(); } @@ -383,44 +377,36 @@ describe('Meeting Activity', () => { // Share the meeting const updates = {}; updates[publicUser.user.id] = 'member'; - RestAPI.MeetingsJitsi.updateMembers( - privateUser.restContext, - meeting.id, - updates, - err => { - assert.ok(!err); + RestAPI.MeetingsJitsi.updateMembers(privateUser.restContext, meeting.id, updates, err => { + assert.ok(!err); - // Collect a second time the email queue - EmailTestsUtil.collectAndFetchAllEmails(emails => { - // There should be exactly one email - assert.strictEqual(emails.length, 1); + // Collect a second time the email queue + EmailTestsUtil.collectAndFetchAllEmails(emails => { + // There should be exactly one email + assert.strictEqual(emails.length, 1); - const stringEmail = JSON.stringify(emails[0]); - const email = emails[0]; + const stringEmail = JSON.stringify(emails[0]); + const email = emails[0]; - // Sanity check that the email is to the shared target - assert.strictEqual(email.to[0].address, publicUser.user.email); + // Sanity check that the email is to the shared target + assert.strictEqual(email.to[0].address, publicUser.user.email); - // Ensure some data expected to be in the email is there - assert.notStrictEqual( - stringEmail.indexOf(privateUser.restContext.hostHeader), - -1 - ); - assert.notStrictEqual(stringEmail.indexOf(meeting.profilePath), -1); - assert.notStrictEqual(stringEmail.indexOf(meeting.displayName), -1); + // Ensure some data expected to be in the email is there + assert.notStrictEqual(stringEmail.indexOf(privateUser.restContext.hostHeader), -1); + assert.notStrictEqual(stringEmail.indexOf(meeting.profilePath), -1); + assert.notStrictEqual(stringEmail.indexOf(meeting.displayName), -1); - // Ensure private data is nowhere to be found - assert.strictEqual(stringEmail.indexOf(privateUser.user.displayName), -1); - assert.strictEqual(stringEmail.indexOf(privateUser.user.email), -1); - assert.strictEqual(stringEmail.indexOf(privateUser.user.locale), -1); + // Ensure private data is nowhere to be found + assert.strictEqual(stringEmail.indexOf(privateUser.user.displayName), -1); + assert.strictEqual(stringEmail.indexOf(privateUser.user.email), -1); + assert.strictEqual(stringEmail.indexOf(privateUser.user.locale), -1); - // Ensure the public alias of the private user is present - assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); + // Ensure the public alias of the private user is present + assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); - return callback(); - }); - } - ); + return callback(); + }); + }); }); } ); @@ -450,44 +436,36 @@ describe('Meeting Activity', () => { EmailTestsUtil.collectAndFetchAllEmails(emails => { // Update the meeting's metadata const updates = { displayName: 'new-display-name' }; - RestAPI.MeetingsJitsi.updateMeeting( - privateUser.restContext, - meeting.id, - updates, - (err, meeting) => { - assert.ok(!err); + RestAPI.MeetingsJitsi.updateMeeting(privateUser.restContext, meeting.id, updates, (err, meeting) => { + assert.ok(!err); - // Collect a second time the email queue - EmailTestsUtil.collectAndFetchAllEmails(emails => { - // There should be exactly one email - assert.strictEqual(emails.length, 1); + // Collect a second time the email queue + EmailTestsUtil.collectAndFetchAllEmails(emails => { + // There should be exactly one email + assert.strictEqual(emails.length, 1); - const stringEmail = JSON.stringify(emails[0]); - const email = emails[0]; + const stringEmail = JSON.stringify(emails[0]); + const email = emails[0]; - // Sanity check that the email is to the shared target - assert.strictEqual(email.to[0].address, publicUser.user.email); + // Sanity check that the email is to the shared target + assert.strictEqual(email.to[0].address, publicUser.user.email); - // Ensure some data expected to be in the email is there - assert.notStrictEqual( - stringEmail.indexOf(privateUser.restContext.hostHeader), - -1 - ); - assert.notStrictEqual(stringEmail.indexOf(meeting.profilePath), -1); - assert.notStrictEqual(stringEmail.indexOf(meeting.displayName), -1); + // Ensure some data expected to be in the email is there + assert.notStrictEqual(stringEmail.indexOf(privateUser.restContext.hostHeader), -1); + assert.notStrictEqual(stringEmail.indexOf(meeting.profilePath), -1); + assert.notStrictEqual(stringEmail.indexOf(meeting.displayName), -1); - // Ensure private data is nowhere to be found - assert.strictEqual(stringEmail.indexOf(privateUser.user.displayName), -1); - assert.strictEqual(stringEmail.indexOf(privateUser.user.email), -1); - assert.strictEqual(stringEmail.indexOf(privateUser.user.locale), -1); + // Ensure private data is nowhere to be found + assert.strictEqual(stringEmail.indexOf(privateUser.user.displayName), -1); + assert.strictEqual(stringEmail.indexOf(privateUser.user.email), -1); + assert.strictEqual(stringEmail.indexOf(privateUser.user.locale), -1); - // Ensure the public alias of the private user is present - assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); + // Ensure the public alias of the private user is present + assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); - return callback(); - }); - } - ); + return callback(); + }); + }); }); } ); @@ -514,45 +492,36 @@ describe('Meeting Activity', () => { // Collect a first time the email queue to empty it EmailTestsUtil.collectAndFetchAllEmails(emails => { // Post a comment - RestAPI.MeetingsJitsi.createComment( - privateUser.restContext, - meeting.id, - 'Hello world !', - null, - err => { - assert.ok(!err); + RestAPI.MeetingsJitsi.createComment(privateUser.restContext, meeting.id, 'Hello world !', null, err => { + assert.ok(!err); - // Collect a second time the email queue - EmailTestsUtil.collectAndFetchAllEmails(emails => { - // There should be exactly one email - assert.strictEqual(emails.length, 1); + // Collect a second time the email queue + EmailTestsUtil.collectAndFetchAllEmails(emails => { + // There should be exactly one email + assert.strictEqual(emails.length, 1); - const stringEmail = JSON.stringify(emails[0]); - const email = emails[0]; + const stringEmail = JSON.stringify(emails[0]); + const email = emails[0]; - // Sanity check that the email is to the shared target - assert.strictEqual(email.to[0].address, publicUser.user.email); + // Sanity check that the email is to the shared target + assert.strictEqual(email.to[0].address, publicUser.user.email); - // Ensure some data expected to be in the email is there - assert.notStrictEqual( - stringEmail.indexOf(privateUser.restContext.hostHeader), - -1 - ); - assert.notStrictEqual(stringEmail.indexOf(meeting.profilePath), -1); - assert.notStrictEqual(stringEmail.indexOf(meeting.displayName), -1); + // Ensure some data expected to be in the email is there + assert.notStrictEqual(stringEmail.indexOf(privateUser.restContext.hostHeader), -1); + assert.notStrictEqual(stringEmail.indexOf(meeting.profilePath), -1); + assert.notStrictEqual(stringEmail.indexOf(meeting.displayName), -1); - // Ensure private data is nowhere to be found - assert.strictEqual(stringEmail.indexOf(privateUser.user.displayName), -1); - assert.strictEqual(stringEmail.indexOf(privateUser.user.email), -1); - assert.strictEqual(stringEmail.indexOf(privateUser.user.locale), -1); + // Ensure private data is nowhere to be found + assert.strictEqual(stringEmail.indexOf(privateUser.user.displayName), -1); + assert.strictEqual(stringEmail.indexOf(privateUser.user.email), -1); + assert.strictEqual(stringEmail.indexOf(privateUser.user.locale), -1); - // Ensure the public alias of the private user is present - assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); + // Ensure the public alias of the private user is present + assert.notStrictEqual(stringEmail.indexOf('swappedFromPublicAlias'), -1); - return callback(); - }); - } - ); + return callback(); + }); + }); }); } ); diff --git a/packages/oae-jitsi/tests/test-library-search.js b/packages/oae-jitsi/tests/test-library-search.js index 9ece497666..fc3647c414 100644 --- a/packages/oae-jitsi/tests/test-library-search.js +++ b/packages/oae-jitsi/tests/test-library-search.js @@ -1,9 +1,9 @@ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const RestAPI = require('oae-rest'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); +import * as RestAPI from 'oae-rest'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests'; describe('Meeting Library Search', () => { // REST contexts we can use to do REST requests diff --git a/packages/oae-jitsi/tests/test-library.js b/packages/oae-jitsi/tests/test-library.js index 1df21bc342..2c330d801c 100644 --- a/packages/oae-jitsi/tests/test-library.js +++ b/packages/oae-jitsi/tests/test-library.js @@ -1,10 +1,8 @@ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const LibraryAPI = require('oae-library'); -const RestAPI = require('oae-rest'); - -const TestsUtil = require('oae-tests'); +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; describe('Meeting libraries', () => { let camAnonymousRestCtx = null; @@ -32,131 +30,117 @@ describe('Meeting libraries', () => { // Create an user with the proper visibility TestsUtil.generateTestUsers(restCtx, 1, (err, users) => { const user = _.values(users)[0]; - RestAPI.User.updateUser( - user.restContext, - user.user.id, - { visibility: userVisibility }, - err => { - assert.ok(!err); - - // Fill up the user library with 3 meeting items - RestAPI.MeetingsJitsi.createMeeting( - user.restContext, - 'name', - 'description', - false, - false, - 'private', - null, - null, - (err, privateMeeting) => { - assert.ok(!err); - - RestAPI.MeetingsJitsi.createMeeting( - user.restContext, - 'name', - 'description', - false, - false, - 'loggedin', - null, - null, - (err, loggedinMeeting) => { - assert.ok(!err); - - RestAPI.MeetingsJitsi.createMeeting( - user.restContext, - 'name', - 'description', - false, - false, - 'public', - null, - null, - (err, publicMeeting) => { - assert.ok(!err); - - return callback(user, privateMeeting, loggedinMeeting, publicMeeting); - } - ); - } - ); - } - ); - } - ); - }); - }; - - /** - * Creates a group and fills its library with meeting items. - * - * @param {RestContext} restCtx The context with which to create the user and content - * @param {String} groupLibrary The visibility for the new group - * @param {Function} callback Standard callback function - * @param {User} callback.user The created user - * @param {Meeting} callback.privateMeeting The private meeting - * @param {Meeting} callback.loggedinMeeting The loggedin meeting - * @param {Meeting} callback.publicMeeting The public meeting - */ - const createGroupAndLibrary = function(restCtx, groupVisibility, callback) { - RestAPI.Group.createGroup( - restCtx, - 'displayName', - 'description', - groupVisibility, - 'no', - [], - [], - (err, group) => { + RestAPI.User.updateUser(user.restContext, user.user.id, { visibility: userVisibility }, err => { assert.ok(!err); - // Fill up the group library with 3 meeting items + // Fill up the user library with 3 meeting items RestAPI.MeetingsJitsi.createMeeting( - restCtx, + user.restContext, 'name', 'description', false, false, 'private', - [group.id], + null, null, (err, privateMeeting) => { assert.ok(!err); RestAPI.MeetingsJitsi.createMeeting( - restCtx, + user.restContext, 'name', 'description', false, false, 'loggedin', - [group.id], + null, null, (err, loggedinMeeting) => { assert.ok(!err); RestAPI.MeetingsJitsi.createMeeting( - restCtx, + user.restContext, 'name', 'description', false, false, 'public', - [group.id], + null, null, (err, publicMeeting) => { assert.ok(!err); - return callback(group, privateMeeting, loggedinMeeting, publicMeeting); + return callback(user, privateMeeting, loggedinMeeting, publicMeeting); } ); } ); } ); - } - ); + }); + }); + }; + + /** + * Creates a group and fills its library with meeting items. + * + * @param {RestContext} restCtx The context with which to create the user and content + * @param {String} groupLibrary The visibility for the new group + * @param {Function} callback Standard callback function + * @param {User} callback.user The created user + * @param {Meeting} callback.privateMeeting The private meeting + * @param {Meeting} callback.loggedinMeeting The loggedin meeting + * @param {Meeting} callback.publicMeeting The public meeting + */ + const createGroupAndLibrary = function(restCtx, groupVisibility, callback) { + RestAPI.Group.createGroup(restCtx, 'displayName', 'description', groupVisibility, 'no', [], [], (err, group) => { + assert.ok(!err); + + // Fill up the group library with 3 meeting items + RestAPI.MeetingsJitsi.createMeeting( + restCtx, + 'name', + 'description', + false, + false, + 'private', + [group.id], + null, + (err, privateMeeting) => { + assert.ok(!err); + + RestAPI.MeetingsJitsi.createMeeting( + restCtx, + 'name', + 'description', + false, + false, + 'loggedin', + [group.id], + null, + (err, loggedinMeeting) => { + assert.ok(!err); + + RestAPI.MeetingsJitsi.createMeeting( + restCtx, + 'name', + 'description', + false, + false, + 'public', + [group.id], + null, + (err, publicMeeting) => { + assert.ok(!err); + + return callback(group, privateMeeting, loggedinMeeting, publicMeeting); + } + ); + } + ); + } + ); + }); }; /** @@ -195,62 +179,44 @@ describe('Meeting libraries', () => { const users = {}; beforeEach(callback => { - createUserAndLibrary( - camAdminRestCtx, - 'private', - (user, privateMeeting, loggedinMeeting, publicMeeting) => { - users.private = { + createUserAndLibrary(camAdminRestCtx, 'private', (user, privateMeeting, loggedinMeeting, publicMeeting) => { + users.private = { + user, + privateMeeting, + loggedinMeeting, + publicMeeting + }; + + createUserAndLibrary(camAdminRestCtx, 'loggedin', (user, privateMeeting, loggedinMeeting, publicMeeting) => { + users.loggedin = { user, privateMeeting, loggedinMeeting, publicMeeting }; - createUserAndLibrary( - camAdminRestCtx, - 'loggedin', - (user, privateMeeting, loggedinMeeting, publicMeeting) => { - users.loggedin = { - user, - privateMeeting, - loggedinMeeting, - publicMeeting - }; - - createUserAndLibrary( - camAdminRestCtx, - 'public', - (user, privateMeeting, loggedinMeeting, publicMeeting) => { - users.public = { - user, - privateMeeting, - loggedinMeeting, - publicMeeting - }; + createUserAndLibrary(camAdminRestCtx, 'public', (user, privateMeeting, loggedinMeeting, publicMeeting) => { + users.public = { + user, + privateMeeting, + loggedinMeeting, + publicMeeting + }; - return callback(); - } - ); - } - ); - } - ); + return callback(); + }); + }); + }); }); it('should only send the public stream of public users for an anonymous user', callback => { - checkLibrary( - camAnonymousRestCtx, - users.public.user.user.id, - true, - [users.public.publicMeeting], - () => { - checkLibrary(camAnonymousRestCtx, users.loggedin.user.user.id, false, [], () => { - checkLibrary(camAnonymousRestCtx, users.private.user.user.id, false, [], () => { - return callback(); - }); + checkLibrary(camAnonymousRestCtx, users.public.user.user.id, true, [users.public.publicMeeting], () => { + checkLibrary(camAnonymousRestCtx, users.loggedin.user.user.id, false, [], () => { + checkLibrary(camAnonymousRestCtx, users.private.user.user.id, false, [], () => { + return callback(); }); - } - ); + }); + }); }); it('should only send the loggedin stream of public and loggedin users for a loggedin user on the same tenant', callback => { @@ -283,31 +249,13 @@ describe('Meeting libraries', () => { TestsUtil.generateTestUsers(gtAdminRestCtx, 1, (err, myUsers) => { const otherTenantUser = _.values(myUsers)[0]; - checkLibrary( - otherTenantUser.restContext, - users.public.user.user.id, - true, - [users.public.publicMeeting], - () => { - checkLibrary( - otherTenantUser.restContext, - users.loggedin.user.user.id, - false, - [], - () => { - checkLibrary( - otherTenantUser.restContext, - users.private.user.user.id, - false, - [], - () => { - return callback(); - } - ); - } - ); - } - ); + checkLibrary(otherTenantUser.restContext, users.public.user.user.id, true, [users.public.publicMeeting], () => { + checkLibrary(otherTenantUser.restContext, users.loggedin.user.user.id, false, [], () => { + checkLibrary(otherTenantUser.restContext, users.private.user.user.id, false, [], () => { + return callback(); + }); + }); + }); }); }); @@ -322,21 +270,13 @@ describe('Meeting libraries', () => { users.loggedin.user.restContext, users.loggedin.user.user.id, true, - [ - users.loggedin.privateMeeting, - users.loggedin.loggedinMeeting, - users.loggedin.publicMeeting - ], + [users.loggedin.privateMeeting, users.loggedin.loggedinMeeting, users.loggedin.publicMeeting], () => { checkLibrary( users.public.user.restContext, users.public.user.user.id, true, - [ - users.public.privateMeeting, - users.public.loggedinMeeting, - users.public.publicMeeting - ], + [users.public.privateMeeting, users.public.loggedinMeeting, users.public.publicMeeting], () => { return callback(); } @@ -365,59 +305,38 @@ describe('Meeting libraries', () => { assert.ok(!err); // Seed mrvisser's and nicolaas's meeting libraries to ensure it does not get built from scratch - RestAPI.MeetingsJitsi.getMeetingsLibrary( - mrvisser.restContext, - mrvisser.user.id, - err => { + RestAPI.MeetingsJitsi.getMeetingsLibrary(mrvisser.restContext, mrvisser.user.id, err => { + assert.ok(!err); + + RestAPI.MeetingsJitsi.getMeetingsLibrary(nicolaas.restContext, nicolaas.user.id, err => { assert.ok(!err); - RestAPI.MeetingsJitsi.getMeetingsLibrary( - nicolaas.restContext, - nicolaas.user.id, - err => { + // Make nicolaas a member of the meeting + const updates = {}; + updates[nicolaas.user.id] = 'member'; + + RestAPI.MeetingsJitsi.updateMembers(mrvisser.restContext, meeting.id, updates, err => { + assert.ok(!err); + + // Ensure the meeting is still in mrvisser's and nicolaas's meeting libraries + RestAPI.MeetingsJitsi.getMeetingsLibrary(mrvisser.restContext, mrvisser.user.id, (err, result) => { assert.ok(!err); + let libraryEntry = result.results[0]; + assert.ok(libraryEntry); + assert.strictEqual(libraryEntry.id, meeting.id); - // Make nicolaas a member of the meeting - const updates = {}; - updates[nicolaas.user.id] = 'member'; - - RestAPI.MeetingsJitsi.updateMembers( - mrvisser.restContext, - meeting.id, - updates, - err => { - assert.ok(!err); - - // Ensure the meeting is still in mrvisser's and nicolaas's meeting libraries - RestAPI.MeetingsJitsi.getMeetingsLibrary( - mrvisser.restContext, - mrvisser.user.id, - (err, result) => { - assert.ok(!err); - let libraryEntry = result.results[0]; - assert.ok(libraryEntry); - assert.strictEqual(libraryEntry.id, meeting.id); + RestAPI.MeetingsJitsi.getMeetingsLibrary(nicolaas.restContext, nicolaas.user.id, (err, result) => { + assert.ok(!err); + libraryEntry = result.results[0]; + assert.ok(libraryEntry); + assert.strictEqual(libraryEntry.id, meeting.id); - RestAPI.MeetingsJitsi.getMeetingsLibrary( - nicolaas.restContext, - nicolaas.user.id, - (err, result) => { - assert.ok(!err); - libraryEntry = result.results[0]; - assert.ok(libraryEntry); - assert.strictEqual(libraryEntry.id, meeting.id); - - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + return callback(); + }); + }); + }); + }); + }); } ); }); @@ -429,62 +348,44 @@ describe('Meeting libraries', () => { const groups = {}; beforeEach(callback => { - createGroupAndLibrary( - camAdminRestCtx, - 'private', - (group, privateMeeting, loggedinMeeting, publicMeeting) => { - groups.private = { + createGroupAndLibrary(camAdminRestCtx, 'private', (group, privateMeeting, loggedinMeeting, publicMeeting) => { + groups.private = { + group, + privateMeeting, + loggedinMeeting, + publicMeeting + }; + + createGroupAndLibrary(camAdminRestCtx, 'loggedin', (group, privateMeeting, loggedinMeeting, publicMeeting) => { + groups.loggedin = { group, privateMeeting, loggedinMeeting, publicMeeting }; - createGroupAndLibrary( - camAdminRestCtx, - 'loggedin', - (group, privateMeeting, loggedinMeeting, publicMeeting) => { - groups.loggedin = { - group, - privateMeeting, - loggedinMeeting, - publicMeeting - }; - - createGroupAndLibrary( - camAdminRestCtx, - 'public', - (group, privateMeeting, loggedinMeeting, publicMeeting) => { - groups.public = { - group, - privateMeeting, - loggedinMeeting, - publicMeeting - }; + createGroupAndLibrary(camAdminRestCtx, 'public', (group, privateMeeting, loggedinMeeting, publicMeeting) => { + groups.public = { + group, + privateMeeting, + loggedinMeeting, + publicMeeting + }; - return callback(); - } - ); - } - ); - } - ); + return callback(); + }); + }); + }); }); it('should only send the public stream of public groups for an anonymous user', callback => { - checkLibrary( - camAnonymousRestCtx, - groups.public.group.id, - true, - [groups.public.publicMeeting], - () => { - checkLibrary(camAnonymousRestCtx, groups.loggedin.group.id, false, [], () => { - checkLibrary(camAnonymousRestCtx, groups.private.group.id, false, [], () => { - return callback(); - }); + checkLibrary(camAnonymousRestCtx, groups.public.group.id, true, [groups.public.publicMeeting], () => { + checkLibrary(camAnonymousRestCtx, groups.loggedin.group.id, false, [], () => { + checkLibrary(camAnonymousRestCtx, groups.private.group.id, false, [], () => { + return callback(); }); - } - ); + }); + }); }); it('should only send the loggedin stream of public and loggedin groups for a loggedin user on the same tenant', callback => { @@ -519,25 +420,13 @@ describe('Meeting libraries', () => { assert.ok(!err); const anotherTenantUser = _.values(users)[0]; - checkLibrary( - anotherTenantUser.restContext, - groups.public.group.id, - true, - [groups.public.publicMeeting], - () => { - checkLibrary(anotherTenantUser.restContext, groups.loggedin.group.id, false, [], () => { - checkLibrary( - anotherTenantUser.restContext, - groups.private.group.id, - false, - [], - () => { - return callback(); - } - ); + checkLibrary(anotherTenantUser.restContext, groups.public.group.id, true, [groups.public.publicMeeting], () => { + checkLibrary(anotherTenantUser.restContext, groups.loggedin.group.id, false, [], () => { + checkLibrary(anotherTenantUser.restContext, groups.private.group.id, false, [], () => { + return callback(); }); - } - ); + }); + }); }); }); @@ -552,21 +441,13 @@ describe('Meeting libraries', () => { camAdminRestCtx, groups.loggedin.group.id, true, - [ - groups.loggedin.publicMeeting, - groups.loggedin.loggedinMeeting, - groups.loggedin.privateMeeting - ], + [groups.loggedin.publicMeeting, groups.loggedin.loggedinMeeting, groups.loggedin.privateMeeting], () => { checkLibrary( camAdminRestCtx, groups.private.group.id, true, - [ - groups.private.publicMeeting, - groups.private.loggedinMeeting, - groups.private.privateMeeting - ], + [groups.private.publicMeeting, groups.private.loggedinMeeting, groups.private.privateMeeting], () => { return callback(); } @@ -605,59 +486,42 @@ describe('Meeting libraries', () => { assert.ok(!err); // Seed mrvisser's and the group's meeting libraries to ensure it does not get built from scratch - RestAPI.MeetingsJitsi.getMeetingsLibrary( - mrvisser.restContext, - mrvisser.user.id, - err => { + RestAPI.MeetingsJitsi.getMeetingsLibrary(mrvisser.restContext, mrvisser.user.id, err => { + assert.ok(!err); + + RestAPI.MeetingsJitsi.getMeetingsLibrary(mrvisser.restContext, group.id, err => { assert.ok(!err); - RestAPI.MeetingsJitsi.getMeetingsLibrary( - mrvisser.restContext, - group.id, - err => { - assert.ok(!err); - - // Make the group a member of the meeting - const updates = {}; - updates[group.id] = 'member'; - - RestAPI.MeetingsJitsi.updateMembers( - mrvisser.restContext, - meeting.id, - updates, - err => { + // Make the group a member of the meeting + const updates = {}; + updates[group.id] = 'member'; + + RestAPI.MeetingsJitsi.updateMembers(mrvisser.restContext, meeting.id, updates, err => { + assert.ok(!err); + + // Ensure the meeting is still in mrvisser's and the group's meeting libraries + RestAPI.MeetingsJitsi.getMeetingsLibrary( + mrvisser.restContext, + mrvisser.user.id, + (err, result) => { + assert.ok(!err); + let libraryEntry = result.results[0]; + assert.ok(libraryEntry); + assert.strictEqual(libraryEntry.id, meeting.id); + + RestAPI.MeetingsJitsi.getMeetingsLibrary(mrvisser.restContext, group.id, (err, result) => { assert.ok(!err); + libraryEntry = result.results[0]; + assert.ok(libraryEntry); + assert.strictEqual(libraryEntry.id, meeting.id); - // Ensure the meeting is still in mrvisser's and the group's meeting libraries - RestAPI.MeetingsJitsi.getMeetingsLibrary( - mrvisser.restContext, - mrvisser.user.id, - (err, result) => { - assert.ok(!err); - let libraryEntry = result.results[0]; - assert.ok(libraryEntry); - assert.strictEqual(libraryEntry.id, meeting.id); - - RestAPI.MeetingsJitsi.getMeetingsLibrary( - mrvisser.restContext, - group.id, - (err, result) => { - assert.ok(!err); - libraryEntry = result.results[0]; - assert.ok(libraryEntry); - assert.strictEqual(libraryEntry.id, meeting.id); - - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + return callback(); + }); + } + ); + }); + }); + }); } ); } diff --git a/packages/oae-jitsi/tests/test-meetings.js b/packages/oae-jitsi/tests/test-meetings.js index 80c66b4940..05e9f5d6fa 100644 --- a/packages/oae-jitsi/tests/test-meetings.js +++ b/packages/oae-jitsi/tests/test-meetings.js @@ -1,11 +1,10 @@ -const assert = require('assert'); -const _ = require('underscore'); -const async = require('async'); +import assert from 'assert'; +import _ from 'underscore'; +import async from 'async'; -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); - -const MeetingsDAO = require('oae-jitsi/lib/internal/dao'); +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as MeetingsDAO from 'oae-jitsi/lib/internal/dao'; describe('Meeting Jitsi', () => { let camAnonymousRestCtx = null; @@ -70,56 +69,50 @@ describe('Meeting Jitsi', () => { assert.strictEqual(meeting.resourceType, 'meeting-jitsi'); // Check the meeting members and their roles - RestAPI.MeetingsJitsi.getMembers( - loulou.restContext, - meeting.id, - null, - 1000, - (err, members) => { - assert.ok(!err); + RestAPI.MeetingsJitsi.getMembers(loulou.restContext, meeting.id, null, 1000, (err, members) => { + assert.ok(!err); - const memberIds = _.pluck(_.pluck(members.results, 'profile'), 'id'); - - assert.strictEqual(memberIds.length, 3); - assert.strictEqual(_.contains(memberIds, riri.user.id), true); - assert.strictEqual(_.contains(memberIds, fifi.user.id), true); - assert.strictEqual(_.contains(memberIds, loulou.user.id), true); - - const roles = _.pluck(members.results, 'role'); - - assert.strictEqual(roles.length, 3); - assert.strictEqual(_.contains(roles, 'manager'), true); - assert.strictEqual(_.contains(roles, 'member'), true); - - // Ensure the new number of meetings in db is numMeetingsOrig + 1 - let numMeetingAfter = 0; - let hasNewMeeting = false; - - MeetingsDAO.iterateAll( - null, - 1000, - (meetingRows, done) => { - if (meetingRows) { - numMeetingAfter += meetingRows.length; - _.each(meetingRows, meetingRow => { - if (meetingRow.id === meeting.id) { - hasNewMeeting = true; - } - }); - } - - return done(); - }, - err => { - assert.ok(!err); - assert.strictEqual(numMeetingsOrig + 1, numMeetingAfter); - assert.ok(hasNewMeeting); + const memberIds = _.pluck(_.pluck(members.results, 'profile'), 'id'); + + assert.strictEqual(memberIds.length, 3); + assert.strictEqual(_.contains(memberIds, riri.user.id), true); + assert.strictEqual(_.contains(memberIds, fifi.user.id), true); + assert.strictEqual(_.contains(memberIds, loulou.user.id), true); + + const roles = _.pluck(members.results, 'role'); - return callback(); + assert.strictEqual(roles.length, 3); + assert.strictEqual(_.contains(roles, 'manager'), true); + assert.strictEqual(_.contains(roles, 'member'), true); + + // Ensure the new number of meetings in db is numMeetingsOrig + 1 + let numMeetingAfter = 0; + let hasNewMeeting = false; + + MeetingsDAO.iterateAll( + null, + 1000, + (meetingRows, done) => { + if (meetingRows) { + numMeetingAfter += meetingRows.length; + _.each(meetingRows, meetingRow => { + if (meetingRow.id === meeting.id) { + hasNewMeeting = true; + } + }); } - ); - } - ); + + return done(); + }, + err => { + assert.ok(!err); + assert.strictEqual(numMeetingsOrig + 1, numMeetingAfter); + assert.ok(hasNewMeeting); + + return callback(); + } + ); + }); } ); } @@ -159,45 +152,33 @@ describe('Meeting Jitsi', () => { [ /* eslint-disable-next-line func-names */ function checkRiri(done) { - RestAPI.MeetingsJitsi.getMeetingsLibrary( - riri.restContext, - riri.user.id, - (err, meetings) => { - assert.ok(!err); - assert.strictEqual(meetings.results.length, 1); - assert.strictEqual(meetings.results[0].id, meeting.id); + RestAPI.MeetingsJitsi.getMeetingsLibrary(riri.restContext, riri.user.id, (err, meetings) => { + assert.ok(!err); + assert.strictEqual(meetings.results.length, 1); + assert.strictEqual(meetings.results[0].id, meeting.id); - return done(); - } - ); + return done(); + }); }, /* eslint-disable-next-line func-names */ function checkFifi(done) { - RestAPI.MeetingsJitsi.getMeetingsLibrary( - riri.restContext, - riri.user.id, - (err, meetings) => { - assert.ok(!err); - assert.strictEqual(meetings.results.length, 1); - assert.strictEqual(meetings.results[0].id, meeting.id); + RestAPI.MeetingsJitsi.getMeetingsLibrary(riri.restContext, riri.user.id, (err, meetings) => { + assert.ok(!err); + assert.strictEqual(meetings.results.length, 1); + assert.strictEqual(meetings.results[0].id, meeting.id); - return done(); - } - ); + return done(); + }); }, /* eslint-disable-next-line func-names */ function checkLoulou(done) { - RestAPI.MeetingsJitsi.getMeetingsLibrary( - riri.restContext, - riri.user.id, - (err, meetings) => { - assert.ok(!err); - assert.strictEqual(meetings.results.length, 1); - assert.strictEqual(meetings.results[0].id, meeting.id); + RestAPI.MeetingsJitsi.getMeetingsLibrary(riri.restContext, riri.user.id, (err, meetings) => { + assert.ok(!err); + assert.strictEqual(meetings.results.length, 1); + assert.strictEqual(meetings.results[0].id, meeting.id); - return done(); - } - ); + return done(); + }); } ], callback @@ -531,18 +512,13 @@ describe('Meeting Jitsi', () => { contactList: true }; - RestAPI.MeetingsJitsi.updateMeeting( - riri.restContext, - meeting.id, - updates, - (err, meeting) => { - assert.ok(!err); - assert.strictEqual(meeting.displayName, updates.displayName); - assert.strictEqual(meeting.description, updates.description); + RestAPI.MeetingsJitsi.updateMeeting(riri.restContext, meeting.id, updates, (err, meeting) => { + assert.ok(!err); + assert.strictEqual(meeting.displayName, updates.displayName); + assert.strictEqual(meeting.description, updates.description); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -576,17 +552,12 @@ describe('Meeting Jitsi', () => { description: 'new-description' }; - RestAPI.MeetingsJitsi.updateMeeting( - riri.restContext, - meeting.id, - updates, - (err, meeting) => { - assert.ok(err); - assert.strictEqual(err.code, 400); + RestAPI.MeetingsJitsi.updateMeeting(riri.restContext, meeting.id, updates, (err, meeting) => { + assert.ok(err); + assert.strictEqual(err.code, 400); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -620,17 +591,12 @@ describe('Meeting Jitsi', () => { description: 'new-description' }; - RestAPI.MeetingsJitsi.updateMeeting( - riri.restContext, - meeting.id, - updates, - (err, meeting) => { - assert.ok(err); - assert.strictEqual(err.code, 400); + RestAPI.MeetingsJitsi.updateMeeting(riri.restContext, meeting.id, updates, (err, meeting) => { + assert.ok(err); + assert.strictEqual(err.code, 400); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -664,17 +630,12 @@ describe('Meeting Jitsi', () => { description: TestsUtil.generateRandomText(1000) }; - RestAPI.MeetingsJitsi.updateMeeting( - riri.restContext, - meeting.id, - updates, - (err, meeting) => { - assert.ok(err); - assert.strictEqual(err.code, 400); + RestAPI.MeetingsJitsi.updateMeeting(riri.restContext, meeting.id, updates, (err, meeting) => { + assert.ok(err); + assert.strictEqual(err.code, 400); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -705,17 +666,12 @@ describe('Meeting Jitsi', () => { const updates = {}; - RestAPI.MeetingsJitsi.updateMeeting( - riri.restContext, - meeting.id, - updates, - (err, meeting) => { - assert.ok(err); - assert.strictEqual(err.code, 400); + RestAPI.MeetingsJitsi.updateMeeting(riri.restContext, meeting.id, updates, (err, meeting) => { + assert.ok(err); + assert.strictEqual(err.code, 400); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -749,17 +705,12 @@ describe('Meeting Jitsi', () => { chat: 'not-an-valid-value' }; - RestAPI.MeetingsJitsi.updateMeeting( - riri.restContext, - meeting.id, - updates, - (err, meeting) => { - assert.ok(err); - assert.strictEqual(err.code, 400); + RestAPI.MeetingsJitsi.updateMeeting(riri.restContext, meeting.id, updates, (err, meeting) => { + assert.ok(err); + assert.strictEqual(err.code, 400); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -793,17 +744,12 @@ describe('Meeting Jitsi', () => { contactList: 'not-an-valid-value' }; - RestAPI.MeetingsJitsi.updateMeeting( - riri.restContext, - meeting.id, - updates, - (err, meeting) => { - assert.ok(err); - assert.strictEqual(err.code, 400); + RestAPI.MeetingsJitsi.updateMeeting(riri.restContext, meeting.id, updates, (err, meeting) => { + assert.ok(err); + assert.strictEqual(err.code, 400); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -837,17 +783,12 @@ describe('Meeting Jitsi', () => { description: 'new-description' }; - RestAPI.MeetingsJitsi.updateMeeting( - riri.restContext, - 'not-an-id', - updates, - (err, meeting) => { - assert.ok(err); - assert.strictEqual(err.code, 400); + RestAPI.MeetingsJitsi.updateMeeting(riri.restContext, 'not-an-id', updates, (err, meeting) => { + assert.ok(err); + assert.strictEqual(err.code, 400); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -882,17 +823,12 @@ describe('Meeting Jitsi', () => { 'not-an-valid-field-name': 'test' }; - RestAPI.MeetingsJitsi.updateMeeting( - riri.restContext, - meeting.id, - updates, - (err, meeting) => { - assert.ok(err); - assert.strictEqual(err.code, 400); + RestAPI.MeetingsJitsi.updateMeeting(riri.restContext, meeting.id, updates, (err, meeting) => { + assert.ok(err); + assert.strictEqual(err.code, 400); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -926,17 +862,12 @@ describe('Meeting Jitsi', () => { description: 'new-description' }; - RestAPI.MeetingsJitsi.updateMeeting( - camAnonymousRestCtx, - meeting.id, - updates, - (err, meeting) => { - assert.ok(err); - assert.strictEqual(err.code, 401); + RestAPI.MeetingsJitsi.updateMeeting(camAnonymousRestCtx, meeting.id, updates, (err, meeting) => { + assert.ok(err); + assert.strictEqual(err.code, 401); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -972,17 +903,12 @@ describe('Meeting Jitsi', () => { description: 'new-description' }; - RestAPI.MeetingsJitsi.updateMeeting( - fifi.restContext, - meeting.id, - updates, - (err, meeting) => { - assert.ok(err); - assert.strictEqual(err.code, 401); + RestAPI.MeetingsJitsi.updateMeeting(fifi.restContext, meeting.id, updates, (err, meeting) => { + assert.ok(err); + assert.strictEqual(err.code, 401); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -1019,17 +945,12 @@ describe('Meeting Jitsi', () => { description: 'new-description' }; - RestAPI.MeetingsJitsi.updateMeeting( - fifi.restContext, - meeting.id, - updates, - (err, meeting) => { - assert.ok(err); - assert.strictEqual(err.code, 401); + RestAPI.MeetingsJitsi.updateMeeting(fifi.restContext, meeting.id, updates, (err, meeting) => { + assert.ok(err); + assert.strictEqual(err.code, 401); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -1159,31 +1080,23 @@ describe('Meeting Jitsi', () => { [ /* eslint-disable-next-line func-names */ function ririCheck(done) { - RestAPI.MeetingsJitsi.getMeetingsLibrary( - riri.restContext, - riri.user.id, - (err, meetings) => { - assert.ok(!err); - assert.strictEqual(meetings.results.length, 1); - assert.strictEqual(meetings.results[0].id, meeting2.id); + RestAPI.MeetingsJitsi.getMeetingsLibrary(riri.restContext, riri.user.id, (err, meetings) => { + assert.ok(!err); + assert.strictEqual(meetings.results.length, 1); + assert.strictEqual(meetings.results[0].id, meeting2.id); - return done(); - } - ); + return done(); + }); }, /* eslint-disable-next-line func-names */ function fifiCheck(done) { - RestAPI.MeetingsJitsi.getMeetingsLibrary( - fifi.restContext, - fifi.user.id, - (err, meetings) => { - assert.ok(!err); - assert.strictEqual(meetings.results.length, 1); - assert.strictEqual(meetings.results[0].id, meeting2.id); + RestAPI.MeetingsJitsi.getMeetingsLibrary(fifi.restContext, fifi.user.id, (err, meetings) => { + assert.ok(!err); + assert.strictEqual(meetings.results.length, 1); + assert.strictEqual(meetings.results[0].id, meeting2.id); - return done(); - } - ); + return done(); + }); }, /* eslint-disable-next-line func-names */ function loulouCheck(done) { @@ -1356,17 +1269,12 @@ describe('Meeting Jitsi', () => { const updates = {}; updates[fifi.user.id] = 'member'; - RestAPI.MeetingsJitsi.updateMembers( - riri.restContext, - 'not-a-valid-id', - updates, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); + RestAPI.MeetingsJitsi.updateMembers(riri.restContext, 'not-a-valid-id', updates, err => { + assert.ok(err); + assert.strictEqual(err.code, 400); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -1564,23 +1472,17 @@ describe('Meeting Jitsi', () => { const body = 'Hello world'; const replyTo = null; - RestAPI.MeetingsJitsi.createComment( - riri.restContext, - meeting.id, - body, - replyTo, - (err, comment) => { - assert.ok(!err); - assert.strictEqual(comment.createdBy.id, riri.user.id); - assert.strictEqual(comment.level, 0); - assert.strictEqual(comment.body, body); - assert.strictEqual(comment.messageBoxId, meeting.id); - assert.ok(comment.id); - assert.ok(comment.created); + RestAPI.MeetingsJitsi.createComment(riri.restContext, meeting.id, body, replyTo, (err, comment) => { + assert.ok(!err); + assert.strictEqual(comment.createdBy.id, riri.user.id); + assert.strictEqual(comment.level, 0); + assert.strictEqual(comment.body, body); + assert.strictEqual(comment.messageBoxId, meeting.id); + assert.ok(comment.id); + assert.ok(comment.created); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -1617,28 +1519,22 @@ describe('Meeting Jitsi', () => { const body = 'Hello world'; const replyTo = null; - RestAPI.MeetingsJitsi.createComment( - riri.restContext, - meeting.id, - body, - replyTo, - (err, comment) => { - assert.ok(!err); + RestAPI.MeetingsJitsi.createComment(riri.restContext, meeting.id, body, replyTo, (err, comment) => { + assert.ok(!err); - // Add a response to the previous comment - RestAPI.MeetingsJitsi.createComment( - fifi.restContext, - meeting.id, - 'Hello riri', - comment.created, - (err, comment) => { - assert.ok(!err); + // Add a response to the previous comment + RestAPI.MeetingsJitsi.createComment( + fifi.restContext, + meeting.id, + 'Hello riri', + comment.created, + (err, comment) => { + assert.ok(!err); - return callback(); - } - ); - } - ); + return callback(); + } + ); + }); } ); }); @@ -1717,18 +1613,12 @@ describe('Meeting Jitsi', () => { const body = ''; const replyTo = null; - RestAPI.MeetingsJitsi.createComment( - riri.restContext, - meeting.id, - body, - replyTo, - (err, comment) => { - assert.ok(err); - assert.strictEqual(err.code, 400); + RestAPI.MeetingsJitsi.createComment(riri.restContext, meeting.id, body, replyTo, (err, comment) => { + assert.ok(err); + assert.strictEqual(err.code, 400); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -1762,18 +1652,12 @@ describe('Meeting Jitsi', () => { const body = 'Hello World'; const replyTo = 'not-an-existing-reply-to-timestamp'; - RestAPI.MeetingsJitsi.createComment( - riri.restContext, - meeting.id, - body, - replyTo, - (err, comment) => { - assert.ok(err); - assert.strictEqual(err.code, 400); + RestAPI.MeetingsJitsi.createComment(riri.restContext, meeting.id, body, replyTo, (err, comment) => { + assert.ok(err); + assert.strictEqual(err.code, 400); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -1807,18 +1691,12 @@ describe('Meeting Jitsi', () => { const body = TestsUtil.generateRandomText(10000); const replyTo = null; - RestAPI.MeetingsJitsi.createComment( - riri.restContext, - meeting.id, - body, - replyTo, - (err, comment) => { - assert.ok(err); - assert.strictEqual(err.code, 400); + RestAPI.MeetingsJitsi.createComment(riri.restContext, meeting.id, body, replyTo, (err, comment) => { + assert.ok(err); + assert.strictEqual(err.code, 400); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -1852,18 +1730,12 @@ describe('Meeting Jitsi', () => { const body = 'Hello world'; const replyTo = null; - RestAPI.MeetingsJitsi.createComment( - camAnonymousRestCtx, - meeting.id, - body, - replyTo, - (err, comment) => { - assert.ok(err); - assert.strictEqual(err.code, 401); + RestAPI.MeetingsJitsi.createComment(camAnonymousRestCtx, meeting.id, body, replyTo, (err, comment) => { + assert.ok(err); + assert.strictEqual(err.code, 401); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -1899,18 +1771,12 @@ describe('Meeting Jitsi', () => { const body = 'Hello world'; const replyTo = null; - RestAPI.MeetingsJitsi.createComment( - fifi.restContext, - meeting.id, - body, - replyTo, - (err, comment) => { - assert.ok(err); - assert.strictEqual(err.code, 401); + RestAPI.MeetingsJitsi.createComment(fifi.restContext, meeting.id, body, replyTo, (err, comment) => { + assert.ok(err); + assert.strictEqual(err.code, 401); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -1946,17 +1812,11 @@ describe('Meeting Jitsi', () => { const body = 'Hello world'; const replyTo = null; - RestAPI.MeetingsJitsi.createComment( - fifi.restContext, - meeting.id, - body, - replyTo, - (err, comment) => { - assert.ok(!err); + RestAPI.MeetingsJitsi.createComment(fifi.restContext, meeting.id, body, replyTo, (err, comment) => { + assert.ok(!err); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -1992,17 +1852,11 @@ describe('Meeting Jitsi', () => { const body = 'Hello world'; const replyTo = null; - RestAPI.MeetingsJitsi.createComment( - fifi.restContext, - meeting.id, - body, - replyTo, - (err, comment) => { - assert.ok(!err); + RestAPI.MeetingsJitsi.createComment(fifi.restContext, meeting.id, body, replyTo, (err, comment) => { + assert.ok(!err); - return callback(); - } - ); + return callback(); + }); } ); }); @@ -2038,26 +1892,15 @@ describe('Meeting Jitsi', () => { const body = 'Hello world'; const replyTo = null; - RestAPI.MeetingsJitsi.createComment( - riri.restContext, - meeting.id, - body, - replyTo, - (err, comment) => { - assert.ok(!err); + RestAPI.MeetingsJitsi.createComment(riri.restContext, meeting.id, body, replyTo, (err, comment) => { + assert.ok(!err); - RestAPI.MeetingsJitsi.deleteComment( - riri.restContext, - meeting.id, - comment.created, - (err, softDeleted) => { - assert.ok(!err); + RestAPI.MeetingsJitsi.deleteComment(riri.restContext, meeting.id, comment.created, (err, softDeleted) => { + assert.ok(!err); - return callback(); - } - ); - } - ); + return callback(); + }); + }); } ); }); @@ -2091,38 +1934,32 @@ describe('Meeting Jitsi', () => { const body = 'Hello world'; const replyTo = null; - RestAPI.MeetingsJitsi.createComment( - riri.restContext, - meeting.id, - body, - replyTo, - (err, comment1) => { - assert.ok(!err); + RestAPI.MeetingsJitsi.createComment(riri.restContext, meeting.id, body, replyTo, (err, comment1) => { + assert.ok(!err); - RestAPI.MeetingsJitsi.createComment( - riri.restContext, - meeting.id, - 'Hello Riri', - comment1.created, - (err, comment2) => { - assert.ok(!err); + RestAPI.MeetingsJitsi.createComment( + riri.restContext, + meeting.id, + 'Hello Riri', + comment1.created, + (err, comment2) => { + assert.ok(!err); - RestAPI.MeetingsJitsi.deleteComment( - riri.restContext, - meeting.id, - comment1.created, - (err, softDeleted) => { - assert.ok(!err); - assert.ok(softDeleted.deleted); - assert.ok(!softDeleted.body); + RestAPI.MeetingsJitsi.deleteComment( + riri.restContext, + meeting.id, + comment1.created, + (err, softDeleted) => { + assert.ok(!err); + assert.ok(softDeleted.deleted); + assert.ok(!softDeleted.body); - return callback(); - } - ); - } - ); - } - ); + return callback(); + } + ); + } + ); + }); } ); }); @@ -2156,27 +1993,21 @@ describe('Meeting Jitsi', () => { const body = 'Hello world'; const replyTo = null; - RestAPI.MeetingsJitsi.createComment( - riri.restContext, - meeting.id, - body, - replyTo, - (err, comment) => { - assert.ok(!err); + RestAPI.MeetingsJitsi.createComment(riri.restContext, meeting.id, body, replyTo, (err, comment) => { + assert.ok(!err); - RestAPI.MeetingsJitsi.deleteComment( - riri.restContext, - 'not-a-valid-meeting-id', - comment.created, - (err, softDeleted) => { - assert.ok(err); - assert.strictEqual(err.code, 400); + RestAPI.MeetingsJitsi.deleteComment( + riri.restContext, + 'not-a-valid-meeting-id', + comment.created, + (err, softDeleted) => { + assert.ok(err); + assert.strictEqual(err.code, 400); - return callback(); - } - ); - } - ); + return callback(); + } + ); + }); } ); }); @@ -2210,27 +2041,21 @@ describe('Meeting Jitsi', () => { const body = 'Hello world'; const replyTo = null; - RestAPI.MeetingsJitsi.createComment( - riri.restContext, - meeting.id, - body, - replyTo, - (err, comment) => { - assert.ok(!err); + RestAPI.MeetingsJitsi.createComment(riri.restContext, meeting.id, body, replyTo, (err, comment) => { + assert.ok(!err); - RestAPI.MeetingsJitsi.deleteComment( - riri.restContext, - meeting.id, - 'not-a-valid-comment-timestamp', - (err, softDeleted) => { - assert.ok(err); - assert.strictEqual(err.code, 400); + RestAPI.MeetingsJitsi.deleteComment( + riri.restContext, + meeting.id, + 'not-a-valid-comment-timestamp', + (err, softDeleted) => { + assert.ok(err); + assert.strictEqual(err.code, 400); - return callback(); - } - ); - } - ); + return callback(); + } + ); + }); } ); }); diff --git a/packages/oae-jitsi/tests/test-push.js b/packages/oae-jitsi/tests/test-push.js index b0f95ccfbe..c0b8058288 100644 --- a/packages/oae-jitsi/tests/test-push.js +++ b/packages/oae-jitsi/tests/test-push.js @@ -1,12 +1,12 @@ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const ActivityTestsUtil = require('oae-activity/lib/test/util'); -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); +import * as ActivityTestsUtil from 'oae-activity/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; -const { ActivityConstants } = require('oae-activity/lib/constants'); -const { MeetingsConstants } = require('oae-jitsi/lib/constants'); +import { ActivityConstants } from 'oae-activity/lib/constants'; +import { MeetingsConstants } from 'oae-jitsi/lib/constants'; describe('Meeting Push', () => { let localAdminRestContext = null; @@ -15,9 +15,7 @@ describe('Meeting Push', () => { * Function that will fill up the tenant admin and anymous rest contexts */ before(callback => { - localAdminRestContext = TestsUtil.createTenantAdminRestContext( - global.oaeTests.tenants.localhost.host - ); + localAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.localhost.host); return callback(); }); @@ -105,17 +103,11 @@ describe('Meeting Push', () => { assert.strictEqual(err.code, 401); // Sanity check that a valid signature works - client.subscribe( - meeting.id, - 'activity', - meeting.signature, - null, - err => { - assert.ok(!err); - - return callback(); - } - ); + client.subscribe(meeting.id, 'activity', meeting.signature, null, err => { + assert.ok(!err); + + return callback(); + }); } ); } diff --git a/packages/oae-jitsi/tests/test-search.js b/packages/oae-jitsi/tests/test-search.js index 7bcdbba93d..9c0f73310e 100644 --- a/packages/oae-jitsi/tests/test-search.js +++ b/packages/oae-jitsi/tests/test-search.js @@ -1,10 +1,10 @@ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const RestAPI = require('oae-rest'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as RestAPI from 'oae-rest'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests'; describe('Meeting Search', () => { // REST contexts we can use to do REST requests diff --git a/packages/oae-library/lib/api.authz.js b/packages/oae-library/lib/api.authz.js index 981794f9eb..2326fb1d7e 100644 --- a/packages/oae-library/lib/api.authz.js +++ b/packages/oae-library/lib/api.authz.js @@ -13,10 +13,11 @@ * permissions and limitations under the License. */ -const AuthzAPI = require('oae-authz'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzUtil = require('oae-authz/lib/util'); -const TenantsUtil = require('oae-tenants/lib/util'); +import * as AuthzAPI from 'oae-authz'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as TenantsUtil from 'oae-tenants/lib/util'; + +import { AuthzConstants } from 'oae-authz/lib/constants'; /** * Determine which visibility level of library the user in context should receive from the target library owner. The following @@ -59,6 +60,7 @@ const resolveTargetLibraryAccess = function(ctx, libraryId, libraryOwner, callba if (err) { return callback(err); } + if (implicitRole === roleHigh) { // We are implicitly a manager (i.e., we are administrator of the library's tenant) return callback(null, true, AuthzConstants.visibility.PRIVATE); @@ -73,21 +75,25 @@ const resolveTargetLibraryAccess = function(ctx, libraryId, libraryOwner, callba if (err) { return callback(err); } + if (hasAnyRole) { // If the current user has an explicit role on the library resource, they can always // see private items return callback(null, true, AuthzConstants.visibility.PRIVATE); } + if (implicitRole && TenantsUtil.isLoggedIn(ctx, libraryOwner.tenant.alias)) { // If we have implicit access and we can are logged in to the library's tenant, we // can see loggedin items return callback(null, true, AuthzConstants.visibility.LOGGEDIN); } + if (implicitRole) { // If we have implicit access but aren't authenticated to the library's tenant, we // can see public items return callback(null, true, AuthzConstants.visibility.PUBLIC); } + if ( ctx.user() && TenantsUtil.canInteract(ctx.user().tenant.alias, libraryOwner.tenant.alias) && @@ -139,7 +145,4 @@ const resolveLibraryBucketVisibility = function(libraryId, resource) { return effectiveVisibility; }; -module.exports = { - resolveTargetLibraryAccess, - resolveLibraryBucketVisibility -}; +export { resolveTargetLibraryAccess, resolveLibraryBucketVisibility }; diff --git a/packages/oae-library/lib/api.index.js b/packages/oae-library/lib/api.index.js index 3b47de8eea..ae5c550c06 100644 --- a/packages/oae-library/lib/api.index.js +++ b/packages/oae-library/lib/api.index.js @@ -13,17 +13,18 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const { AuthzConstants } = require('oae-authz/lib/constants'); -const Cassandra = require('oae-util/lib/cassandra'); -const Counter = require('oae-util/lib/counter'); -const log = require('oae-logger').logger('library-index'); -const OaeUtil = require('oae-util/lib/util'); +import * as Cassandra from 'oae-util/lib/cassandra'; +import Counter from 'oae-util/lib/counter'; +import * as OaeUtil from 'oae-util/lib/util'; +import { logger } from 'oae-logger'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as LibraryAuthz from './api.authz'; +import * as LibraryRegistry from './internal/registry'; -const LibraryAuthz = require('./api.authz'); -const LibraryRegistry = require('./internal/registry'); +const log = logger('library-index'); // We need a slug column name to denote a fresh library index at both the lower // bound and upper bound to determine if an index is fresh or invalidated @@ -469,11 +470,13 @@ const _query = function(indexName, libraryId, visibility, opts, callback) { if (err) { return callback(err); } + if (_isStaleLibraryIndex(opts.start, internalLimit, rows)) { if (opts.rebuildIfNecessary) { // If we've specified to rebuild a stale index, rebuild it and try to query again return _rebuildAndQuery(indexName, libraryId, visibility, opts, callback); } + // If we have not specified to rebuild and this index is stale, then warn that something funny is going on log().warn( { @@ -715,6 +718,7 @@ const _isStaleLibraryIndex = function(start, limit, rows) { // refresh the index because it indicates this index has been purged and not yet rebuilt return true; } + if (rows.length < limit && !slugLowColumn) { // If we exhausted the entries and the last entry wasn't the low-bound slug, then we have a // purged index and need to rebuild it @@ -842,14 +846,4 @@ const _splitRankedResourceId = function(rankedResourceId, callback) { return callback(parts.slice(1).join('#'), parts[0]); }; -module.exports = { - registerLibraryIndex, - insert, - update, - remove, - whenUpdatesComplete, - list, - purge, - isStale -}; -// Const LibraryIndex = module.exports; +export { registerLibraryIndex, insert, update, remove, whenUpdatesComplete, list, purge, isStale }; diff --git a/packages/oae-library/lib/api.js b/packages/oae-library/lib/api.js index b95bcbccc1..5d7b146c73 100644 --- a/packages/oae-library/lib/api.js +++ b/packages/oae-library/lib/api.js @@ -13,6 +13,8 @@ * permissions and limitations under the License. */ -module.exports.Index = require('./api.index'); -module.exports.Authz = require('./api.authz'); -module.exports.Search = require('./api.search'); +import * as Index from './api.index'; +import * as Authz from './api.authz'; +import * as Search from './api.search'; + +export { Index, Authz, Search }; diff --git a/packages/oae-library/lib/api.search.js b/packages/oae-library/lib/api.search.js index 218fd963a2..0cd06acd77 100644 --- a/packages/oae-library/lib/api.search.js +++ b/packages/oae-library/lib/api.search.js @@ -13,18 +13,18 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const { ContentConstants } = require('oae-content/lib/constants'); -const { DiscussionsConstants } = require('oae-discussions/lib/constants'); -const OaeUtil = require('oae-util/lib/util'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const { SearchConstants } = require('oae-search/lib/constants'); -const SearchAPI = require('oae-search'); -const SearchUtil = require('oae-search/lib/util'); -const { Validator } = require('oae-util/lib/validator'); - -const LibraryAPI = require('oae-library'); +import _ from 'underscore'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { ContentConstants } from 'oae-content/lib/constants'; +import { DiscussionsConstants } from 'oae-discussions/lib/constants'; +import { SearchConstants } from 'oae-search/lib/constants'; +import { Validator } from 'oae-util/lib/validator'; + +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as SearchAPI from 'oae-search'; +import * as SearchUtil from 'oae-search/lib/util'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as LibraryAPI from 'oae-library'; /** * Register a search that searches through a user or group library. @@ -51,7 +51,7 @@ const LibraryAPI = require('oae-library'); * @param {Function} [options.searches.loggedin] The function to use to derive the filter for the loggedin library bucket. See `options.searches.public` parameter for function parameters * @param {Function} [options.searches.private] The function to use to derive the filter for the private library bucket. See `options.searches.public` parameter for function parameters */ -module.exports.registerLibrarySearch = function(searchName, resourceTypes, options) { +export const registerLibrarySearch = function(searchName, resourceTypes, options) { options = options || {}; options.searches = options.searches || {}; options.getLibraryOwner = options.getLibraryOwner || PrincipalsDAO.getPrincipal; @@ -74,6 +74,7 @@ module.exports.registerLibrarySearch = function(searchName, resourceTypes, optio if (err) { return callback(err); } + if (libraryOwner.deleted) { return callback({ code: 404, msg: 'The library was not found' }); } @@ -88,6 +89,7 @@ module.exports.registerLibrarySearch = function(searchName, resourceTypes, optio if (err) { return callback(err); } + if (!canAccess) { return callback({ code: 401, @@ -194,10 +196,7 @@ const _defaultLibraryFilter = function(resourceTypes, visibility, association) { ) ); } else { - filter = SearchUtil.filterAnd( - baseFilter, - SearchUtil.filterTerm('visibility', AuthzConstants.visibility.PUBLIC) - ); + filter = SearchUtil.filterAnd(baseFilter, SearchUtil.filterTerm('visibility', AuthzConstants.visibility.PUBLIC)); } return callback(null, filter); diff --git a/packages/oae-library/lib/init.js b/packages/oae-library/lib/init.js index bf9240e7c6..9616893743 100644 --- a/packages/oae-library/lib/init.js +++ b/packages/oae-library/lib/init.js @@ -13,6 +13,6 @@ * permissions and limitations under the License. */ -module.exports = function(config, callback) { +export function init(config, callback) { return callback(); -}; +} diff --git a/packages/oae-library/lib/internal/registry.js b/packages/oae-library/lib/internal/registry.js index bf590334a3..78b0023895 100644 --- a/packages/oae-library/lib/internal/registry.js +++ b/packages/oae-library/lib/internal/registry.js @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; const libraryIndexes = {}; @@ -26,15 +26,10 @@ const registerLibraryIndex = function(name, options) { options = options || {}; if (libraryIndexes[name]) { - throw new Error( - util.format('Attempted to register duplicate library index with name "%s"', name) - ); + throw new Error(util.format('Attempted to register duplicate library index with name "%s"', name)); } else if (!_.isFunction(options.pageResources)) { throw new TypeError( - util.format( - 'Attempted to register library index "%s" that has no "pageResources" function', - name - ) + util.format('Attempted to register library index "%s" that has no "pageResources" function', name) ); } @@ -51,7 +46,4 @@ const getRegisteredLibraryIndex = function(name) { return libraryIndexes[name]; }; -module.exports = { - registerLibraryIndex, - getRegisteredLibraryIndex -}; +export { registerLibraryIndex, getRegisteredLibraryIndex }; diff --git a/packages/oae-library/lib/migration.js b/packages/oae-library/lib/migration.js index a85c856e85..ea4a81f357 100644 --- a/packages/oae-library/lib/migration.js +++ b/packages/oae-library/lib/migration.js @@ -1,4 +1,4 @@ -const Cassandra = require('oae-util/lib/cassandra'); +import { createColumnFamilies } from 'oae-util/lib/cassandra'; /** * Ensure that the all of the library-related schemas are created. If they already exist, this method will not do anything @@ -8,7 +8,7 @@ const Cassandra = require('oae-util/lib/cassandra'); * @api private */ const ensureSchema = function(callback) { - Cassandra.createColumnFamilies( + createColumnFamilies( { LibraryIndex: 'CREATE TABLE "LibraryIndex" ("bucketKey" text, "rankedResourceId" text, "value" text, PRIMARY KEY ("bucketKey", "rankedResourceId")) WITH COMPACT STORAGE' @@ -17,4 +17,4 @@ const ensureSchema = function(callback) { ); }; -module.exports = { ensureSchema }; +export { ensureSchema }; diff --git a/packages/oae-library/lib/test/util.js b/packages/oae-library/lib/test/util.js index 9ee92a1d18..7dc972264d 100644 --- a/packages/oae-library/lib/test/util.js +++ b/packages/oae-library/lib/test/util.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const LibraryAPI = require('oae-library'); +import * as LibraryAPI from 'oae-library'; /** * Purge a library, ensuring that before it is purged that it was "fresh" (i.e., not stale). Useful @@ -88,7 +88,4 @@ const _assertPurgeFreshLibraries = function(indexName, libraryIds, callback) { }); }; -module.exports = { - assertPurgeFreshLibraries, - assertNotStale -}; +export { assertPurgeFreshLibraries, assertNotStale }; diff --git a/packages/oae-library/tests/test-index.js b/packages/oae-library/tests/test-index.js index ac3561ac5a..0fe151e829 100644 --- a/packages/oae-library/tests/test-index.js +++ b/packages/oae-library/tests/test-index.js @@ -13,11 +13,10 @@ * permissions and limitations under the License. */ -const assert = require('assert'); +import assert from 'assert'; -const TestsUtil = require('oae-tests/lib/util'); - -const LibraryAPI = require('oae-library'); +import * as TestsUtil from 'oae-tests/lib/util'; +import * as LibraryAPI from 'oae-library'; describe('Library Indexing', () => { describe('#registerLibraryIndex', () => { @@ -53,14 +52,14 @@ describe('Library Indexing', () => { */ it('verify a library index is cleared when purged and then rebuilt when queried', callback => { /*! - * Convenience function to create a light-weight resource with just an id, tenant alias - * and visibility - * - * @param {String} id The id fo the resource to create - * @param {String} tenantAlias The tenant alias of the resource - * @param {String} visibility The visibility of the resource - * @return {Object} The light weight resource object - */ + * Convenience function to create a light-weight resource with just an id, tenant alias + * and visibility + * + * @param {String} id The id fo the resource to create + * @param {String} tenantAlias The tenant alias of the resource + * @param {String} visibility The visibility of the resource + * @return {Object} The light weight resource object + */ const _resource = function(id, tenantAlias, visibility) { return { id, @@ -111,69 +110,48 @@ describe('Library Indexing', () => { assert.strictEqual(isStale, true); // Query the index and make sure we get the items - LibraryAPI.Index.list( - testName, - 'somelibrary', - 'private', - { limit: 10 }, - (err, entries) => { + LibraryAPI.Index.list(testName, 'somelibrary', 'private', { limit: 10 }, (err, entries) => { + assert.ok(!err); + assert.strictEqual(entries.length, 3); + assert.deepStrictEqual(entries[0], { resourceId: 'c', value: 1 }); + assert.deepStrictEqual(entries[1], { resourceId: 'b', value: ['a', 'b', 'c'] }); + assert.deepStrictEqual(entries[2], { resourceId: 'a', value: 1 }); + + // Ensure that each library index list is no longer stale + LibraryAPI.Index.isStale(testName, 'somelibrary', 'private', (err, isStale) => { assert.ok(!err); - assert.strictEqual(entries.length, 3); - assert.deepStrictEqual(entries[0], { resourceId: 'c', value: 1 }); - assert.deepStrictEqual(entries[1], { resourceId: 'b', value: ['a', 'b', 'c'] }); - assert.deepStrictEqual(entries[2], { resourceId: 'a', value: 1 }); - - // Ensure that each library index list is no longer stale - LibraryAPI.Index.isStale(testName, 'somelibrary', 'private', (err, isStale) => { + assert.strictEqual(isStale, false); + LibraryAPI.Index.isStale(testName, 'somelibrary', 'loggedin', (err, isStale) => { assert.ok(!err); assert.strictEqual(isStale, false); - LibraryAPI.Index.isStale(testName, 'somelibrary', 'loggedin', (err, isStale) => { + LibraryAPI.Index.isStale(testName, 'somelibrary', 'public', (err, isStale) => { assert.ok(!err); assert.strictEqual(isStale, false); - LibraryAPI.Index.isStale(testName, 'somelibrary', 'public', (err, isStale) => { + + // Purge the full library + LibraryAPI.Index.purge(testName, 'somelibrary', err => { assert.ok(!err); - assert.strictEqual(isStale, false); - // Purge the full library - LibraryAPI.Index.purge(testName, 'somelibrary', err => { + // Ensure that each library index list is stale once again + LibraryAPI.Index.isStale(testName, 'somelibrary', 'private', (err, isStale) => { assert.ok(!err); - - // Ensure that each library index list is stale once again - LibraryAPI.Index.isStale( - testName, - 'somelibrary', - 'private', - (err, isStale) => { + assert.strictEqual(isStale, true); + LibraryAPI.Index.isStale(testName, 'somelibrary', 'loggedin', (err, isStale) => { + assert.ok(!err); + assert.strictEqual(isStale, true); + LibraryAPI.Index.isStale(testName, 'somelibrary', 'public', (err, isStale) => { assert.ok(!err); assert.strictEqual(isStale, true); - LibraryAPI.Index.isStale( - testName, - 'somelibrary', - 'loggedin', - (err, isStale) => { - assert.ok(!err); - assert.strictEqual(isStale, true); - LibraryAPI.Index.isStale( - testName, - 'somelibrary', - 'public', - (err, isStale) => { - assert.ok(!err); - assert.strictEqual(isStale, true); - - return callback(); - } - ); - } - ); - } - ); + + return callback(); + }); + }); }); }); }); }); - } - ); + }); + }); }); }); }); diff --git a/packages/oae-logger/lib/api.js b/packages/oae-logger/lib/api.js index 9d5c4e4637..f7d3a2faef 100644 --- a/packages/oae-logger/lib/api.js +++ b/packages/oae-logger/lib/api.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); -const bunyan = require('bunyan'); +import util from 'util'; +import _ from 'underscore'; +import bunyan from 'bunyan'; // The logger to use when no logger is specified const SYSTEM_LOGGER_NAME = 'system'; @@ -143,4 +143,4 @@ const _wrapErrorFunction = function(loggerName, errorFunction) { return wrapperErrorFunction; }; -module.exports = { refreshLogConfiguration, logger }; +export { refreshLogConfiguration, logger }; diff --git a/packages/oae-logger/lib/init.js b/packages/oae-logger/lib/init.js index 6b80856a4d..aecb3719dc 100644 --- a/packages/oae-logger/lib/init.js +++ b/packages/oae-logger/lib/init.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const { refreshLogConfiguration } = require('oae-logger'); +import { refreshLogConfiguration } from 'oae-logger'; -module.exports = function(config, callback) { +export const init = function(config, callback) { refreshLogConfiguration(config.log); callback(); }; diff --git a/packages/oae-logger/tests/test-logger.js b/packages/oae-logger/tests/test-logger.js index 070dee3e56..1d5bf7e9ed 100644 --- a/packages/oae-logger/tests/test-logger.js +++ b/packages/oae-logger/tests/test-logger.js @@ -13,13 +13,14 @@ * permissions and limitations under the License. */ -const util = require('util'); +import util from 'util'; -const assert = require('assert'); -const { logger } = require('oae-logger'); -const RestAPI = require('oae-rest'); -const TelemetryAPI = require('oae-telemetry'); -const TestsUtil = require('oae-tests/lib/util'); +import assert from 'assert'; +import { logger } from 'oae-logger'; + +import * as RestAPI from 'oae-rest'; +import * as TelemetryAPI from 'oae-telemetry'; +import * as TestsUtil from 'oae-tests/lib/util'; describe('Logger', () => { // Rest context that can be used every time we need to make a request as a global admin diff --git a/packages/oae-lti/lib/api.js b/packages/oae-lti/lib/api.js index 54771620e9..1fce094590 100644 --- a/packages/oae-lti/lib/api.js +++ b/packages/oae-lti/lib/api.js @@ -13,20 +13,22 @@ * permissions and limitations under the License. */ -const util = require('util'); -const oauth = require('oauth-sign'); -const ShortId = require('shortid'); +import util from 'util'; +import * as VersionAPI from 'oae-version'; -const AuthzPermissions = require('oae-authz/lib/permissions'); -const AuthzUtil = require('oae-authz/lib/util'); -const log = require('oae-logger').logger('oae-lti'); -const PrincipalsApi = require('oae-principals'); -const { Validator } = require('oae-authz/lib/validator'); -const VersionAPI = require('oae-version'); +import oauth from 'oauth-sign'; +import ShortId from 'shortid'; -const LtiDAO = require('./internal/dao'); -const { LtiToolLaunchParams } = require('./model'); -const { LtiLaunchParams } = require('./model'); +import * as AuthzPermissions from 'oae-authz/lib/permissions'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import { logger } from 'oae-logger'; +import PrincipalsApi from 'oae-principals'; +import { Validator } from 'oae-authz/lib/validator'; + +import * as LtiDAO from './internal/dao'; +import { LtiToolLaunchParams, LtiLaunchParams } from './model'; + +const log = logger('oae-lti'); /** * Get the parameters required to launch an LTI tool @@ -288,9 +290,4 @@ const deleteLtiTool = function(ctx, id, groupId, callback) { }); }; -module.exports = { - getLtiTool, - addLtiTool, - getLtiTools, - deleteLtiTool -}; +export { getLtiTool, addLtiTool, getLtiTools, deleteLtiTool }; diff --git a/packages/oae-lti/lib/init.js b/packages/oae-lti/lib/init.js index ab13d0583d..ec6d29c172 100644 --- a/packages/oae-lti/lib/init.js +++ b/packages/oae-lti/lib/init.js @@ -13,10 +13,12 @@ * permissions and limitations under the License. */ -const log = require('oae-logger').logger('oae-lti-init'); +import { logger } from 'oae-logger'; -module.exports = function(config, callback) { +const log = logger('oae-lti-init'); + +export function init(config, callback) { log().info('Initializing the oae-lti module'); return callback(); -}; +} diff --git a/packages/oae-lti/lib/internal/dao.js b/packages/oae-lti/lib/internal/dao.js index 5a368700e3..0abc8c663a 100644 --- a/packages/oae-lti/lib/internal/dao.js +++ b/packages/oae-lti/lib/internal/dao.js @@ -13,11 +13,10 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const Cassandra = require('oae-util/lib/cassandra'); - -const { LtiTool } = require('oae-lti/lib/model'); +import Cassandra from 'oae-util/lib/cassandra'; +import { LtiTool } from 'oae-lti/lib/model'; /** * Create an LTI tool @@ -33,16 +32,7 @@ const { LtiTool } = require('oae-lti/lib/model'); * @param {Object} callback.err An error that occurred, if any * @param {LtiTool} callback.ltiTool The LTI tool that was created */ -const createLtiTool = function( - id, - groupId, - launchUrl, - secret, - consumerKey, - displayName, - description, - callback -) { +const createLtiTool = function(id, groupId, launchUrl, secret, consumerKey, displayName, description, callback) { displayName = displayName || 'LTI tool'; description = description || ''; @@ -76,23 +66,20 @@ const createLtiTool = function( * @param {LtiTool} callback.ltiTool The request LTI tool object */ const getLtiTool = function(id, groupId, callback) { - Cassandra.runQuery( - 'SELECT * FROM "LtiTools" WHERE "groupId" = ? AND "id" = ?', - [groupId, id], - (err, rows) => { - if (err) { - return callback(err); - } - if (_.isEmpty(rows)) { - return callback({ - code: 404, - msg: 'Could not find LTI tool ' + id + ' for group ' + groupId - }); - } - - return callback(null, _rowToLtiTool(rows[0])); + Cassandra.runQuery('SELECT * FROM "LtiTools" WHERE "groupId" = ? AND "id" = ?', [groupId, id], (err, rows) => { + if (err) { + return callback(err); + } + + if (_.isEmpty(rows)) { + return callback({ + code: 404, + msg: 'Could not find LTI tool ' + id + ' for group ' + groupId + }); } - ); + + return callback(null, _rowToLtiTool(rows[0])); + }); }; /** @@ -131,11 +118,7 @@ const getLtiToolsByGroupId = function(groupId, callback) { * @param {Object} callback.err An error that occurred, if any */ const deleteLtiTool = function(id, groupId, callback) { - Cassandra.runQuery( - 'DELETE FROM "LtiTools" WHERE "groupId" = ? AND "id" = ?', - [groupId, id], - callback - ); + Cassandra.runQuery('DELETE FROM "LtiTools" WHERE "groupId" = ? AND "id" = ?', [groupId, id], callback); }; /** @@ -147,23 +130,11 @@ const deleteLtiTool = function(id, groupId, callback) { */ const _rowToLtiTool = function(row) { const hash = Cassandra.rowToHash(row); - const tool = new LtiTool( - hash.id, - hash.groupId, - hash.launchUrl, - hash.secret, - hash.oauthConsumerKey, - { - displayName: hash.displayName, - description: hash.description - } - ); + const tool = new LtiTool(hash.id, hash.groupId, hash.launchUrl, hash.secret, hash.oauthConsumerKey, { + displayName: hash.displayName, + description: hash.description + }); return tool; }; -module.exports = { - createLtiTool, - getLtiTool, - getLtiToolsByGroupId, - deleteLtiTool -}; +export { createLtiTool, getLtiTool, getLtiToolsByGroupId, deleteLtiTool }; diff --git a/packages/oae-lti/lib/migration.js b/packages/oae-lti/lib/migration.js index 57663b3b6f..717125ec29 100644 --- a/packages/oae-lti/lib/migration.js +++ b/packages/oae-lti/lib/migration.js @@ -1,4 +1,4 @@ -const Cassandra = require('oae-util/lib/cassandra'); +import { createColumnFamilies } from 'oae-util/lib/cassandra'; /** * Create the schema for Learning Tools Interoperability tools @@ -8,7 +8,7 @@ const Cassandra = require('oae-util/lib/cassandra'); * @api private */ const ensureSchema = function(callback) { - Cassandra.createColumnFamilies( + createColumnFamilies( { LtiTools: 'CREATE TABLE "LtiTools" ("id" text, "groupId" text, "launchUrl" text, "secret" text, "oauthConsumerKey" text, "displayName" text, "description" text, PRIMARY KEY ("groupId", "id"))' @@ -17,4 +17,4 @@ const ensureSchema = function(callback) { ); }; -module.exports = { ensureSchema }; +export { ensureSchema }; diff --git a/packages/oae-lti/lib/model.js b/packages/oae-lti/lib/model.js index 0fe95f1d30..c63816e173 100644 --- a/packages/oae-lti/lib/model.js +++ b/packages/oae-lti/lib/model.js @@ -15,7 +15,7 @@ /* eslint-disable camelcase */ -const util = require('util'); +import util from 'util'; /** * The LTI tool model. @@ -29,7 +29,7 @@ const util = require('util'); * @param {Number} [opts.displayName] The name of the LTI tool * @param {Number} [opts.description] A description of the LTI tool */ -module.exports.LtiTool = function(id, groupId, launchUrl, secret, consumerKey, opts) { +const LtiTool = function(id, groupId, launchUrl, secret, consumerKey, opts) { opts = opts || {}; const that = {}; @@ -60,15 +60,7 @@ module.exports.LtiTool = function(id, groupId, launchUrl, secret, consumerKey, o * @param {String} groupId The globally unique id for the group that owns the tool * @param {Object} principal The object representing the user launching the tool */ -module.exports.LtiLaunchParams = function( - tool, - version, - tenantAlias, - groupDisplayName, - isGroupManager, - groupId, - principal -) { +const LtiLaunchParams = function(tool, version, tenantAlias, groupDisplayName, isGroupManager, groupId, principal) { const that = {}; that.oauth_consumer_key = tool.consumerKey; that.oauth_nonce = Date.now(); @@ -111,7 +103,7 @@ module.exports.LtiLaunchParams = function( * @param {LtiTool} tool An LtiTool object * @param {LtiLaunchParams} launchParams An LtiLaunchParams object */ -module.exports.LtiToolLaunchParams = function(tool, launchParams) { +const LtiToolLaunchParams = function(tool, launchParams) { launchParams = launchParams || {}; const that = {}; @@ -120,3 +112,5 @@ module.exports.LtiToolLaunchParams = function(tool, launchParams) { return that; }; + +export { LtiLaunchParams, LtiToolLaunchParams, LtiTool }; diff --git a/packages/oae-lti/lib/rest.js b/packages/oae-lti/lib/rest.js index 644a2b4aef..b8d3bab5da 100644 --- a/packages/oae-lti/lib/rest.js +++ b/packages/oae-lti/lib/rest.js @@ -13,9 +13,8 @@ * permissions and limitations under the License. */ -const OAE = require('oae-util/lib/oae'); - -const LtiApi = require('./api'); +import * as OAE from 'oae-util/lib/oae'; +import * as LtiApi from './api'; /** * @REST getLtiTool diff --git a/packages/oae-lti/tests/test-lti.js b/packages/oae-lti/tests/test-lti.js index e610917ea5..14e5b880de 100644 --- a/packages/oae-lti/tests/test-lti.js +++ b/packages/oae-lti/tests/test-lti.js @@ -13,15 +13,15 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const util = require('util'); -const _ = require('underscore'); +import assert from 'assert'; +import util from 'util'; +import _ from 'underscore'; -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; -const PrincipalsAPI = require('oae-principals'); -const { User } = require('oae-principals/lib/model.user'); +import * as PrincipalsAPI from 'oae-principals'; +import { User } from 'oae-principals/lib/model.user'; describe('LTI tools', () => { // Rest context that can be used to perform requests as different types of users diff --git a/packages/oae-mediacore/lib/internal/util.js b/packages/oae-mediacore/lib/internal/util.js index c24d7fc277..7cbc287ec2 100644 --- a/packages/oae-mediacore/lib/internal/util.js +++ b/packages/oae-mediacore/lib/internal/util.js @@ -17,7 +17,7 @@ const crypto = require('crypto'); const util = require('util'); const request = require('request'); -const MediaCoreConfig = require('oae-config').config('oae-mediacore'); +const MediaCoreConfig = require('oae-config').setUpConfig('oae-mediacore'); /** * Get the MediaCore API values that have been configured in the Admin UI. diff --git a/packages/oae-mediacore/lib/processor.js b/packages/oae-mediacore/lib/processor.js index 26e643a3ae..5dc6d180bc 100644 --- a/packages/oae-mediacore/lib/processor.js +++ b/packages/oae-mediacore/lib/processor.js @@ -21,7 +21,7 @@ const _ = require('underscore'); const log = require('oae-logger').logger('oae-mediacore'); const PreviewConstants = require('oae-preview-processor/lib/constants'); -const MediaCoreConfig = require('oae-config').config('oae-mediacore'); +const MediaCoreConfig = require('oae-config').setUpConfig('oae-mediacore'); const MediaCoreDAO = require('./internal/dao'); const MediaCoreUtil = require('./internal/util'); diff --git a/packages/oae-messagebox/lib/api.js b/packages/oae-messagebox/lib/api.js index c507e5a002..9336828ddc 100644 --- a/packages/oae-messagebox/lib/api.js +++ b/packages/oae-messagebox/lib/api.js @@ -13,19 +13,22 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const Cassandra = require('oae-util/lib/cassandra'); -const EmitterAPI = require('oae-emitter'); -const Locking = require('oae-util/lib/locking'); -const log = require('oae-logger').logger('oae-messagebox-api'); -const OaeUtil = require('oae-util/lib/util'); -const TenantsAPI = require('oae-tenants'); -const { Validator } = require('oae-util/lib/validator'); +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as EmitterAPI from 'oae-emitter'; +import * as Locking from 'oae-util/lib/locking'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as TenantsAPI from 'oae-tenants'; -const MessageBoxModel = require('./model'); -const { MessageBoxConstants } = require('./constants'); +import { logger } from 'oae-logger'; + +import { Validator } from 'oae-util/lib/validator'; +import * as MessageBoxModel from './model'; +import { MessageBoxConstants } from './constants'; + +const log = logger('oae-messagebox-api'); // A contribution will be considered "recent" for 30 days after it occurs const DURATION_RECENT_CONTRIBUTIONS_SECONDS = 30 * 24 * 60 * 60; @@ -103,12 +106,14 @@ const replaceLinks = function(body) { // Check for a match in the target of a markdown link } + if (preURLChar === '(' && postURLChar === ')') { return '(' + path + ')'; // If the URL wasn't wrapped in braces we can assume that it was not provided in // markdown format. If that's the case, we do the conversion ourselves } + return preURLChar + '[' + path + '](' + path + ')' + postURLChar; }); }; @@ -133,9 +138,7 @@ const createMessage = function(messageBoxId, createdBy, body, opts, callback) { const validator = new Validator(); validator.check(messageBoxId, { code: 400, msg: 'A messageBoxId must be specified.' }).notNull(); - validator - .check(createdBy, { code: 400, msg: 'The createdBy parameter must be a valid user id.' }) - .isUserId(); + validator.check(createdBy, { code: 400, msg: 'The createdBy parameter must be a valid user id.' }).isUserId(); validator.check(body, { code: 400, msg: 'The body of the message must be specified.' }).notNull(); if (opts.replyToCreated) { validator @@ -157,6 +160,7 @@ const createMessage = function(messageBoxId, createdBy, body, opts, callback) { }) .max(Date.now()); } + if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -190,9 +194,7 @@ const createMessage = function(messageBoxId, createdBy, body, opts, callback) { } // Derive this message's thread key by appending it to the parent, if applicable. Otherwise, it is a top-level key - const threadKey = replyToThreadKey - ? _appendToThreadKey(replyToThreadKey, created) - : created + '|'; + const threadKey = replyToThreadKey ? _appendToThreadKey(replyToThreadKey, created) : created + '|'; // Replace absolute OAE links with relative ones to avoid cross-tenant // permission issues @@ -207,12 +209,7 @@ const createMessage = function(messageBoxId, createdBy, body, opts, callback) { }; // Create the query that creates the message object - const createMessageQuery = Cassandra.constructUpsertCQL( - 'Messages', - 'id', - messageId, - messageStorageHash - ); + const createMessageQuery = Cassandra.constructUpsertCQL('Messages', 'id', messageId, messageStorageHash); if (!createMessageQuery) { log().error(diagnosticData, 'Failed to create a new message query.'); return callback({ code: 500, msg: 'Failed to create a new message' }); @@ -220,8 +217,7 @@ const createMessage = function(messageBoxId, createdBy, body, opts, callback) { // Create the query that adds the message object to the messagebox const indexMessageQuery = { - query: - 'INSERT INTO "MessageBoxMessages" ("messageBoxId", "threadKey", "value") VALUES (?, ?, ?)', + query: 'INSERT INTO "MessageBoxMessages" ("messageBoxId", "threadKey", "value") VALUES (?, ?, ?)', parameters: [messageBoxId, threadKey, '1'] }; @@ -278,10 +274,12 @@ const _lockUniqueTimestamp = function(id, timestamp, callback) { // This should only occur if Redis is down, just return the requested ts return callback(timestamp, lockToken); } + if (!lockToken) { // Someone else has the requested ts, try to lock one ms later return _lockUniqueTimestamp(id, timestamp + 1, callback); } + // Successful lock, return the details return callback(timestamp, key, lockToken); }); @@ -299,9 +297,7 @@ const _lockUniqueTimestamp = function(id, timestamp, callback) { const updateMessageBody = function(messageBoxId, created, newBody, callback) { const validator = new Validator(); validator.check(messageBoxId, { code: 400, msg: 'A messageBoxId must be specified.' }).notNull(); - validator - .check(created, { code: 400, msg: 'The created parameter must be specified.' }) - .notNull(); + validator.check(created, { code: 400, msg: 'The created parameter must be specified.' }).notNull(); validator .check(created, { code: 400, @@ -314,9 +310,7 @@ const updateMessageBody = function(messageBoxId, created, newBody, callback) { msg: 'The created parameter must be a valid timestamp (integer) that is not in the future.' }) .max(Date.now()); - validator - .check(newBody, { code: 400, msg: 'The new body of the message must be specified.' }) - .notNull(); + validator.check(newBody, { code: 400, msg: 'The new body of the message must be specified.' }).notNull(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -369,18 +363,13 @@ const getMessagesFromMessageBox = function(messageBoxId, start, limit, opts, cal // Will maintain the output order of the messages according to their threadkey const createdTimestamps = _.map(threadKeys, _parseCreatedFromThreadKey); - getMessages( - messageBoxId, - createdTimestamps, - { scrubDeleted: opts.scrubDeleted }, - (err, messages) => { - if (err) { - return callback(err); - } - - return callback(null, messages, nextToken); + getMessages(messageBoxId, createdTimestamps, { scrubDeleted: opts.scrubDeleted }, (err, messages) => { + if (err) { + return callback(err); } - ); + + return callback(null, messages, nextToken); + }); }); }; @@ -408,9 +397,7 @@ const getMessages = function(messageBoxId, createdTimestamps, opts, callback) { _.each(createdTimestamps, timestamp => { validator.check(timestamp, { code: 400, msg: 'A timestamp cannot be null.' }).notNull(); validator.check(timestamp, { code: 400, msg: 'A timestamp should be an integer.' }).isInt(); - validator - .check(timestamp, { code: 400, msg: 'A timestamp cannot be in the future.' }) - .max(Date.now()); + validator.check(timestamp, { code: 400, msg: 'A timestamp cannot be in the future.' }).max(Date.now()); }); if (validator.hasErrors()) { return callback(validator.getFirstError()); @@ -504,12 +491,8 @@ const deleteMessage = function(messageBoxId, createdTimestamp, opts, callback) { const validator = new Validator(); validator.check(messageBoxId, { code: 400, msg: 'A messageBoxId must be specified.' }).notNull(); - validator - .check(createdTimestamp, { code: 400, msg: 'The createdTimestamp should not be null.' }) - .notNull(); - validator - .check(createdTimestamp, { code: 400, msg: 'The createdTimestamp should be an integer.' }) - .isInt(); + validator.check(createdTimestamp, { code: 400, msg: 'The createdTimestamp should not be null.' }).notNull(); + validator.check(createdTimestamp, { code: 400, msg: 'The createdTimestamp should be an integer.' }).isInt(); validator .check(createdTimestamp, { code: 400, msg: 'The createdTimestamp cannot be in the future.' }) .max(Date.now()); @@ -522,6 +505,7 @@ const deleteMessage = function(messageBoxId, createdTimestamp, opts, callback) { }) .isIn(deleteValues); } + if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -530,6 +514,7 @@ const deleteMessage = function(messageBoxId, createdTimestamp, opts, callback) { if (err) { return callback(err); } + if (!messages[0]) { return callback({ code: 404, msg: 'Message not found.' }); } @@ -545,9 +530,11 @@ const deleteMessage = function(messageBoxId, createdTimestamp, opts, callback) { MessageBoxConstants.deleteTypes.HARD ); } + callback(err, MessageBoxConstants.deleteTypes.HARD); }); } + if (opts.deleteType === MessageBoxConstants.deleteTypes.SOFT) { return _softDelete(message, (err, msg) => { if (!err) { @@ -557,16 +544,20 @@ const deleteMessage = function(messageBoxId, createdTimestamp, opts, callback) { MessageBoxConstants.deleteTypes.SOFT ); } + callback(err, MessageBoxConstants.deleteTypes.SOFT, msg); }); } + return _leafDelete(message, (err, deleteType, msg) => { if (!err) { MessageBoxAPI.emit(MessageBoxConstants.events.DELETED_MESSAGE, message.id, deleteType); } + callback(err, deleteType, msg); }); } + return callback({ code: 404, msg: 'The specified message did not exist' }); }); }; @@ -634,21 +625,18 @@ const _getMessageThreadKey = function(messageId, callback) { return callback(); } - Cassandra.runQuery( - 'SELECT "threadKey" FROM "Messages" WHERE "id" = ?', - [messageId], - (err, rows) => { - if (err) { - return callback(err); - } - if (_.isEmpty(rows)) { - // A message by that id may not have existed, simply return undefined - return callback(); - } + Cassandra.runQuery('SELECT "threadKey" FROM "Messages" WHERE "id" = ?', [messageId], (err, rows) => { + if (err) { + return callback(err); + } - return callback(null, rows[0].get('threadKey')); + if (_.isEmpty(rows)) { + // A message by that id may not have existed, simply return undefined + return callback(); } - ); + + return callback(null, rows[0].get('threadKey')); + }); }; /** @@ -765,20 +753,16 @@ const _softDelete = function(message, callback) { const deletedTimestamp = Date.now().toString(); // Set the deleted flag to the current timestamp - Cassandra.runQuery( - 'UPDATE "Messages" SET "deleted" = ? WHERE "id" = ?', - [deletedTimestamp, messageId], - err => { - if (err) { - return callback(err); - } + Cassandra.runQuery('UPDATE "Messages" SET "deleted" = ? WHERE "id" = ?', [deletedTimestamp, messageId], err => { + if (err) { + return callback(err); + } - message.deleted = deletedTimestamp; - message = _scrubMessage(message); + message.deleted = deletedTimestamp; + message = _scrubMessage(message); - return callback(null, message); - } - ); + return callback(null, message); + }); }; /** @@ -938,16 +922,7 @@ const _getLevelFromThreadKey = function(threadKey) { * @api private */ const _scrubMessage = function(message) { - return _.pick( - message, - 'id', - 'messageBoxId', - 'threadKey', - 'created', - 'replyTo', - 'deleted', - 'level' - ); + return _.pick(message, 'id', 'messageBoxId', 'threadKey', 'created', 'replyTo', 'deleted', 'level'); }; /** @@ -964,12 +939,13 @@ const _parseReplyToTimestampFromThreadKey = function(threadKey) { // "timestamp3" is a reply to "timestamp2", so we pick out the second last one in the hierarchy return hierarchy[hierarchy.length - 2]; } + // If we only had 1 element, then this is not a reply at all return null; }; -module.exports = { - emitter: MessageBoxAPI, +export { + MessageBoxAPI as emitter, replaceLinks, createMessage, updateMessageBody, diff --git a/packages/oae-messagebox/lib/constants.js b/packages/oae-messagebox/lib/constants.js index 3abf5c3987..3214916762 100644 --- a/packages/oae-messagebox/lib/constants.js +++ b/packages/oae-messagebox/lib/constants.js @@ -42,4 +42,4 @@ MessageBoxConstants.activity = { IN_REPLY_TO: 'inReplyTo' }; -module.exports = { MessageBoxConstants }; +export { MessageBoxConstants }; diff --git a/packages/oae-messagebox/lib/init.js b/packages/oae-messagebox/lib/init.js index bf9240e7c6..9616893743 100644 --- a/packages/oae-messagebox/lib/init.js +++ b/packages/oae-messagebox/lib/init.js @@ -13,6 +13,6 @@ * permissions and limitations under the License. */ -module.exports = function(config, callback) { +export function init(config, callback) { return callback(); -}; +} diff --git a/packages/oae-messagebox/lib/migration.js b/packages/oae-messagebox/lib/migration.js index 3cdfc2a4de..4b92cf14cb 100644 --- a/packages/oae-messagebox/lib/migration.js +++ b/packages/oae-messagebox/lib/migration.js @@ -1,4 +1,4 @@ -const Cassandra = require('oae-util/lib/cassandra'); +import { createColumnFamilies } from 'oae-util/lib/cassandra'; /* * Ensure that the all of the messages column families are created. If they already exist, this method will not do anything @@ -8,7 +8,7 @@ const Cassandra = require('oae-util/lib/cassandra'); * @api private */ const ensureSchema = function(callback) { - Cassandra.createColumnFamilies( + createColumnFamilies( { Messages: 'CREATE TABLE "Messages" ("id" text PRIMARY KEY, "threadKey" text, "createdBy" text, "body" text, "deleted" text)', @@ -23,4 +23,4 @@ const ensureSchema = function(callback) { ); }; -module.exports = { ensureSchema }; +export { ensureSchema }; diff --git a/packages/oae-messagebox/lib/model.js b/packages/oae-messagebox/lib/model.js index 9fc550b1f4..b250ccf3ee 100644 --- a/packages/oae-messagebox/lib/model.js +++ b/packages/oae-messagebox/lib/model.js @@ -51,17 +51,7 @@ * @param {String} [replyTo] The id of the message to which this message is a reply, if any * @param {Number} [deleted] If the message is soft-deleted, this value will hold the timestamp (millis since the epoch) that it was deleted */ -const Message = function( - id, - messageBoxId, - threadKey, - body, - createdBy, - created, - level, - replyTo, - deleted -) { +const Message = function(id, messageBoxId, threadKey, body, createdBy, created, level, replyTo, deleted) { const that = {}; that.id = id; that.messageBoxId = messageBoxId; @@ -75,4 +65,4 @@ const Message = function( return that; }; -module.exports = { Message }; +export { Message }; diff --git a/packages/oae-messagebox/lib/search.js b/packages/oae-messagebox/lib/search.js index 900f3032b4..d35876e41a 100644 --- a/packages/oae-messagebox/lib/search.js +++ b/packages/oae-messagebox/lib/search.js @@ -13,12 +13,11 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const SearchAPI = require('oae-search'); -const SearchUtil = require('oae-search/lib/util'); - -const MessageBoxAPI = require('oae-messagebox'); +import * as SearchAPI from 'oae-search'; +import * as SearchUtil from 'oae-search/lib/util'; +import * as MessageBoxAPI from 'oae-messagebox'; /** * Register and create a message search document name and schema that is a child of resource documents. Registering @@ -108,26 +107,21 @@ const deleteMessageSearchDocument = function(name, resourceId, message) { */ const _getAllMessages = function(messageBoxId, start, chunkSize, callback, _messages) { _messages = _messages || []; - MessageBoxAPI.getMessagesFromMessageBox( - messageBoxId, - start, - chunkSize, - null, - (err, messages, nextToken) => { - if (err) { - return callback(err); - } + MessageBoxAPI.getMessagesFromMessageBox(messageBoxId, start, chunkSize, null, (err, messages, nextToken) => { + if (err) { + return callback(err); + } - _messages = _.union(_messages, messages); - if (!nextToken) { - return callback(null, _messages); - } - return _getAllMessages(messageBoxId, nextToken, chunkSize, callback, _messages); + _messages = _.union(_messages, messages); + if (!nextToken) { + return callback(null, _messages); } - ); + + return _getAllMessages(messageBoxId, nextToken, chunkSize, callback, _messages); + }); }; -module.exports = { +export { registerMessageSearchDocument, createAllMessageSearchDocuments, createMessageSearchDocuments, diff --git a/packages/oae-messagebox/lib/search/schema/resourceMessagesSchema.js b/packages/oae-messagebox/lib/search/schema/resourceMessagesSchema.js index 17975525c7..ceb62aac09 100644 --- a/packages/oae-messagebox/lib/search/schema/resourceMessagesSchema.js +++ b/packages/oae-messagebox/lib/search/schema/resourceMessagesSchema.js @@ -23,11 +23,9 @@ */ /* eslint-disable unicorn/filename-case */ -module.exports = { - body: { - type: 'string', - store: 'no', - index: 'analyzed', - analyzer: 'message' - } +export const body = { + type: 'string', + store: 'no', + index: 'analyzed', + analyzer: 'message' }; diff --git a/packages/oae-messagebox/lib/util.js b/packages/oae-messagebox/lib/util.js index 97f9c79a43..3926fcf97d 100644 --- a/packages/oae-messagebox/lib/util.js +++ b/packages/oae-messagebox/lib/util.js @@ -13,16 +13,16 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityModel = require('oae-activity/lib/model'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const PrincipalsUtil = require('oae-principals/lib/util'); +import * as ActivityModel from 'oae-activity/lib/model'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; +import { ActivityConstants } from 'oae-activity/lib/constants'; +import * as MessageBoxAPI from './api'; -const MessageBoxAPI = require('./api'); -const { MessageBoxConstants } = require('./constants'); +import { MessageBoxConstants } from './constants'; /** * Creates a bare activity entity that is appropriate for a message. @@ -38,6 +38,7 @@ const createPersistentMessageActivityEntity = function(message, callback) { if (err) { return callback(err); } + message.createdBy = createdByUser; const context = {}; @@ -47,6 +48,7 @@ const createPersistentMessageActivityEntity = function(message, callback) { if (err) { return callback(err); } + if (_.isEmpty(messages) || !messages[0]) { return callback({ code: 404, msg: 'The message could not be found' }); } @@ -106,13 +108,7 @@ const _createPersistentMessageActivityEntity = function(message, context) { */ const transformPersistentMessageActivityEntity = function(ctx, entity, profilePath, urlFormat) { const context = entity.messageContext || {}; - const transformedEntity = _transformMessageActivityEntity( - ctx, - entity, - entity.message, - urlFormat, - profilePath - ); + const transformedEntity = _transformMessageActivityEntity(ctx, entity, entity.message, urlFormat, profilePath); // Transform the parent if there is one if (context.parent) { @@ -149,11 +145,7 @@ const _transformMessageActivityEntity = function(ctx, entity, message, urlFormat opts.url = profilePath; opts.content = message.body; - opts.author = PrincipalsUtil.transformPersistentUserActivityEntity( - ctx, - message.createdBy.id, - message.createdBy - ); + opts.author = PrincipalsUtil.transformPersistentUserActivityEntity(ctx, message.createdBy.id, message.createdBy); opts.published = message.created; opts.ext = {}; @@ -180,7 +172,7 @@ const transformPersistentMessageActivityEntityToInternal = function(ctx, message return message; }; -module.exports = { +export { createPersistentMessageActivityEntity, transformPersistentMessageActivityEntity, transformPersistentMessageActivityEntityToInternal diff --git a/packages/oae-messagebox/tests/test-messagebox.js b/packages/oae-messagebox/tests/test-messagebox.js index 22578c95e4..a47d26cc13 100644 --- a/packages/oae-messagebox/tests/test-messagebox.js +++ b/packages/oae-messagebox/tests/test-messagebox.js @@ -13,15 +13,14 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const util = require('util'); -const _ = require('underscore'); -const ShortId = require('shortid'); +import assert from 'assert'; +import util from 'util'; +import _ from 'underscore'; +import ShortId from 'shortid'; -const TenantsTestUtil = require('oae-tenants/lib/test/util'); -const TestsUtil = require('oae-tests/lib/util'); - -const MessageBoxAPI = require('oae-messagebox'); +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; +import * as TestsUtil from 'oae-tests/lib/util'; +import * as MessageBoxAPI from 'oae-messagebox'; describe('Messagebox', () => { /** @@ -38,7 +37,7 @@ describe('Messagebox', () => { return message.id === id; }); assert.ok(message); - assert.strictEqual(!!message.body, !!body); + assert.strictEqual(Boolean(message.body), Boolean(body)); assert.strictEqual(message.replyTo, replyTo); return message; }; @@ -69,73 +68,54 @@ describe('Messagebox', () => { { replyToCreated: a1Message.created }, (err, a2Message) => { assert.ok(!err); - setTimeout( - MessageBoxAPI.createMessage, - 10, - messageBoxId, - 'u:camtest:foo', - 'B1', - {}, - (err, b1Message) => { + setTimeout(MessageBoxAPI.createMessage, 10, messageBoxId, 'u:camtest:foo', 'B1', {}, (err, b1Message) => { + assert.ok(!err); + setTimeout(MessageBoxAPI.createMessage, 10, messageBoxId, 'u:camtest:foo', 'C1', {}, (err, c1Message) => { assert.ok(!err); setTimeout( MessageBoxAPI.createMessage, 10, messageBoxId, 'u:camtest:foo', - 'C1', - {}, - (err, c1Message) => { + 'A3', + { replyToCreated: a2Message.created }, + (err, a3Message) => { assert.ok(!err); setTimeout( MessageBoxAPI.createMessage, 10, messageBoxId, 'u:camtest:foo', - 'A3', - { replyToCreated: a2Message.created }, - (err, a3Message) => { + 'A4', + { replyToCreated: a1Message.created }, + (err, a4Message) => { assert.ok(!err); - setTimeout( - MessageBoxAPI.createMessage, - 10, - messageBoxId, - 'u:camtest:foo', - 'A4', - { replyToCreated: a1Message.created }, - (err, a4Message) => { - assert.ok(!err); - const tree = { - a1: a1Message, - a2: a2Message, - a3: a3Message, - a4: a4Message, - b1: b1Message, - c1: c1Message - }; - - // Ensuring that all the created timestamps are different. - const createdTimestamps = {}; - _.each(tree, (message, name) => { - // Check the created timestamp has not been set yet. - assert.ok( - !createdTimestamps[message.created], - JSON.stringify(tree, null, 4) - ); - - // Remember this timestamp. - createdTimestamps[message.created] = name; - }); - - callback(messageBoxId, tree); - } - ); + const tree = { + a1: a1Message, + a2: a2Message, + a3: a3Message, + a4: a4Message, + b1: b1Message, + c1: c1Message + }; + + // Ensuring that all the created timestamps are different. + const createdTimestamps = {}; + _.each(tree, (message, name) => { + // Check the created timestamp has not been set yet. + assert.ok(!createdTimestamps[message.created], JSON.stringify(tree, null, 4)); + + // Remember this timestamp. + createdTimestamps[message.created] = name; + }); + + callback(messageBoxId, tree); } ); } ); - } - ); + }); + }); } ); }); @@ -231,76 +211,83 @@ describe('Messagebox', () => { // @see https://github.com/oaeproject/3akai-ux/issues/4086 const newTenantAlias = TenantsTestUtil.generateTestTenantAlias(); const tenantHost = 'abcdefghijklmnop.qrstuvw.xyz'; - TestsUtil.createTenantWithAdmin( - newTenantAlias, - tenantHost, - (err, tenant, tenantAdminRestContext) => { - assert.ok(!err); + TestsUtil.createTenantWithAdmin(newTenantAlias, tenantHost, (err, tenant, tenantAdminRestContext) => { + assert.ok(!err); + + const path = '/path/-Z9+&@#%=~_|!:,.;/file?query=parameter#hash'; + const httpUrl = util.format('http://%s%s', tenantHost, path); + const httpsUrl = util.format('https://%s%s', tenantHost, path); + const markdownPath = util.format('[%s](%s)', path, path); + const markdownHttpUrl = util.format('[%s](%s)', httpUrl, httpUrl); + + const messageBoxId = util.format('msg-box-test-%s', ShortId.generate()); + MessageBoxAPI.createMessage( + messageBoxId, + 'u:camtest:foo', + util.format('URL: %s more', httpUrl), + {}, + (err, message) => { + assert.ok(!err); - const path = '/path/-Z9+&@#%=~_|!:,.;/file?query=parameter#hash'; - const httpUrl = util.format('http://%s%s', tenantHost, path); - const httpsUrl = util.format('https://%s%s', tenantHost, path); - const markdownPath = util.format('[%s](%s)', path, path); - const markdownHttpUrl = util.format('[%s](%s)', httpUrl, httpUrl); - - const messageBoxId = util.format('msg-box-test-%s', ShortId.generate()); - MessageBoxAPI.createMessage( - messageBoxId, - 'u:camtest:foo', - util.format('URL: %s more', httpUrl), - {}, - (err, message) => { + // Verify the link was replaced + MessageBoxAPI.getMessagesFromMessageBox(messageBoxId, null, null, null, (err, messages) => { assert.ok(!err); + verifyMessage(messages[0].id, util.format('URL: %s more', markdownPath), null, messages); - // Verify the link was replaced - MessageBoxAPI.getMessagesFromMessageBox( + // Verify multiple links + MessageBoxAPI.createMessage( messageBoxId, - null, - null, - null, - (err, messages) => { + 'u:camtest:foo', + util.format('URLs: %s %s', httpUrl, httpUrl), + {}, + (err, message) => { assert.ok(!err); - verifyMessage( - messages[0].id, - util.format('URL: %s more', markdownPath), - null, - messages - ); - // Verify multiple links - MessageBoxAPI.createMessage( - messageBoxId, - 'u:camtest:foo', - util.format('URLs: %s %s', httpUrl, httpUrl), - {}, - (err, message) => { - assert.ok(!err); + // Verify the link was replaced + MessageBoxAPI.getMessagesFromMessageBox(messageBoxId, null, null, null, (err, messages) => { + assert.ok(!err); + verifyMessage( + messages[0].id, + util.format('URLs: %s %s', markdownPath, markdownPath), + null, + messages + ); - // Verify the link was replaced - MessageBoxAPI.getMessagesFromMessageBox( - messageBoxId, - null, - null, - null, - (err, messages) => { + // Verify multiple markdown links + MessageBoxAPI.createMessage( + messageBoxId, + 'u:camtest:foo', + util.format('URLs: %s%s', markdownHttpUrl, markdownHttpUrl), + {}, + (err, message) => { + assert.ok(!err); + + // Verify the link was replaced + MessageBoxAPI.getMessagesFromMessageBox(messageBoxId, null, null, null, (err, messages) => { assert.ok(!err); verifyMessage( messages[0].id, - util.format('URLs: %s %s', markdownPath, markdownPath), + util.format('URLs: %s%s', markdownPath, markdownPath), null, messages ); - // Verify multiple markdown links + // Verify that quoted links aren't replaced + const quotedMarkdown = util.format( + '`%s` ` text %s`\n %s\n\n %s\n text %s', + httpsUrl, + httpsUrl, + httpsUrl, + httpsUrl, + httpsUrl + ); MessageBoxAPI.createMessage( messageBoxId, 'u:camtest:foo', - util.format('URLs: %s%s', markdownHttpUrl, markdownHttpUrl), + quotedMarkdown, {}, (err, message) => { assert.ok(!err); - - // Verify the link was replaced MessageBoxAPI.getMessagesFromMessageBox( messageBoxId, null, @@ -308,69 +295,30 @@ describe('Messagebox', () => { null, (err, messages) => { assert.ok(!err); - verifyMessage( - messages[0].id, - util.format('URLs: %s%s', markdownPath, markdownPath), - null, - messages - ); - - // Verify that quoted links aren't replaced - const quotedMarkdown = util.format( + const quotedExpected = util.format( '`%s` ` text %s`\n %s\n\n %s\n text %s', httpsUrl, httpsUrl, - httpsUrl, + markdownPath, httpsUrl, httpsUrl ); - MessageBoxAPI.createMessage( - messageBoxId, - 'u:camtest:foo', - quotedMarkdown, - {}, - (err, message) => { - assert.ok(!err); - MessageBoxAPI.getMessagesFromMessageBox( - messageBoxId, - null, - null, - null, - (err, messages) => { - assert.ok(!err); - const quotedExpected = util.format( - '`%s` ` text %s`\n %s\n\n %s\n text %s', - httpsUrl, - httpsUrl, - markdownPath, - httpsUrl, - httpsUrl - ); - verifyMessage( - messages[0].id, - quotedExpected, - null, - messages - ); - return callback(); - } - ); - } - ); + verifyMessage(messages[0].id, quotedExpected, null, messages); + return callback(); } ); } ); - } - ); - } - ); + }); + } + ); + }); } ); - } - ); - } - ); + }); + } + ); + }); }); /** @@ -388,77 +336,45 @@ describe('Messagebox', () => { assert.ok(!err); // Verify the link was replaced - MessageBoxAPI.getMessagesFromMessageBox( - messageBoxId, - null, - null, - null, - (err, messages) => { - assert.ok(!err); - verifyMessage(messages[0].id, '[URL](' + url + ') more text', null, messages); + MessageBoxAPI.getMessagesFromMessageBox(messageBoxId, null, null, null, (err, messages) => { + assert.ok(!err); + verifyMessage(messages[0].id, '[URL](' + url + ') more text', null, messages); - // Verify a link of the form [http://cambridge.oae.com/foo/bar](http://cambridge.oae.com/foo/bar) - MessageBoxAPI.createMessage( - messageBoxId, - 'u:camtest:foo', - 'URL: [http://cambridge.oae.com' + - url + - '](http://cambridge.oae.com' + - url + - ') more text', - {}, - (err, message) => { + // Verify a link of the form [http://cambridge.oae.com/foo/bar](http://cambridge.oae.com/foo/bar) + MessageBoxAPI.createMessage( + messageBoxId, + 'u:camtest:foo', + 'URL: [http://cambridge.oae.com' + url + '](http://cambridge.oae.com' + url + ') more text', + {}, + (err, message) => { + assert.ok(!err); + + // Verify the link was replaced + MessageBoxAPI.getMessagesFromMessageBox(messageBoxId, null, null, null, (err, messages) => { assert.ok(!err); + verifyMessage(messages[0].id, 'URL: [' + url + '](' + url + ') more text', null, messages); - // Verify the link was replaced - MessageBoxAPI.getMessagesFromMessageBox( + // Verify a link of the form [http://cambridge.oae.com/foo/bar](/foo/bar) + MessageBoxAPI.createMessage( messageBoxId, - null, - null, - null, - (err, messages) => { + 'u:camtest:foo', + 'URL: [http://cambridge.oae.com' + url + '](' + url + ') more text', + {}, + (err, message) => { assert.ok(!err); - verifyMessage( - messages[0].id, - 'URL: [' + url + '](' + url + ') more text', - null, - messages - ); - // Verify a link of the form [http://cambridge.oae.com/foo/bar](/foo/bar) - MessageBoxAPI.createMessage( - messageBoxId, - 'u:camtest:foo', - 'URL: [http://cambridge.oae.com' + url + '](' + url + ') more text', - {}, - (err, message) => { - assert.ok(!err); - - // Verify the link was replaced - MessageBoxAPI.getMessagesFromMessageBox( - messageBoxId, - null, - null, - null, - (err, messages) => { - assert.ok(!err); - verifyMessage( - messages[0].id, - 'URL: [' + url + '](' + url + ') more text', - null, - messages - ); - return callback(); - } - ); - } - ); + // Verify the link was replaced + MessageBoxAPI.getMessagesFromMessageBox(messageBoxId, null, null, null, (err, messages) => { + assert.ok(!err); + verifyMessage(messages[0].id, 'URL: [' + url + '](' + url + ') more text', null, messages); + return callback(); + }); } ); - } - ); - } - ); + }); + } + ); + }); } ); }); @@ -478,46 +394,32 @@ describe('Messagebox', () => { (err, message) => { assert.strictEqual(err.code, 400); - setTimeout( - MessageBoxAPI.createMessage, - 10, - messageBoxId, - 'u:camtest:foo', - 'body', - {}, - (err, message) => { - assert.ok(!err); - assert.ok(message); + setTimeout(MessageBoxAPI.createMessage, 10, messageBoxId, 'u:camtest:foo', 'body', {}, (err, message) => { + assert.ok(!err); + assert.ok(message); - setTimeout( - MessageBoxAPI.createMessage, - 10, - messageBoxId, - 'u:camtest:foo', - 'body', - { replyToCreated: message.created }, - (err, reply) => { - assert.ok(!err); - assert.ok(reply); - assert.strictEqual(reply.replyTo, message.created); + setTimeout( + MessageBoxAPI.createMessage, + 10, + messageBoxId, + 'u:camtest:foo', + 'body', + { replyToCreated: message.created }, + (err, reply) => { + assert.ok(!err); + assert.ok(reply); + assert.strictEqual(reply.replyTo, message.created); - // Sanity check: retrieve them back - MessageBoxAPI.getMessagesFromMessageBox( - messageBoxId, - null, - null, - null, - (err, messages) => { - assert.ok(!err); - verifyMessage(message.id, 'body', null, messages); - verifyMessage(reply.id, 'body', message.created, messages); - return callback(); - } - ); - } - ); - } - ); + // Sanity check: retrieve them back + MessageBoxAPI.getMessagesFromMessageBox(messageBoxId, null, null, null, (err, messages) => { + assert.ok(!err); + verifyMessage(message.id, 'body', null, messages); + verifyMessage(reply.id, 'body', message.created, messages); + return callback(); + }); + } + ); + }); } ); }); @@ -567,20 +469,14 @@ describe('Messagebox', () => { // Update the message. MessageBoxAPI.updateMessageBody(messageBoxId, message.created, 'beta', err => { assert.ok(!err); - MessageBoxAPI.getMessagesFromMessageBox( - messageBoxId, - null, - null, - null, - (err, messages) => { - assert.ok(!err); - // There should still only be 1 message. - assert.strictEqual(messages.length, 1); - // Verify the body has changed. - verifyMessage(message.id, 'beta', null, messages); - return callback(); - } - ); + MessageBoxAPI.getMessagesFromMessageBox(messageBoxId, null, null, null, (err, messages) => { + assert.ok(!err); + // There should still only be 1 message. + assert.strictEqual(messages.length, 1); + // Verify the body has changed. + verifyMessage(message.id, 'beta', null, messages); + return callback(); + }); }); }); }); @@ -601,26 +497,15 @@ describe('Messagebox', () => { verifyMessage(message.id, 'alfa', null, messages); // Update the message - MessageBoxAPI.updateMessageBody( - messageBoxId, - message.created, - 'URL: http://cambridge.oae.com' + url, - err => { + MessageBoxAPI.updateMessageBody(messageBoxId, message.created, 'URL: http://cambridge.oae.com' + url, err => { + assert.ok(!err); + MessageBoxAPI.getMessagesFromMessageBox(messageBoxId, null, null, null, (err, messages) => { assert.ok(!err); - MessageBoxAPI.getMessagesFromMessageBox( - messageBoxId, - null, - null, - null, - (err, messages) => { - assert.ok(!err); - // Verify the body has changed - verifyMessage(message.id, 'URL: [' + url + '](' + url + ')', null, messages); - return callback(); - } - ); - } - ); + // Verify the body has changed + verifyMessage(message.id, 'URL: [' + url + '](' + url + ')', null, messages); + return callback(); + }); + }); }); }); }); @@ -655,28 +540,16 @@ describe('Messagebox', () => { MessageBoxAPI.createMessage(messageBoxId, 'u:camtest:foo', 'alfa', {}, (err, message1) => { assert.ok(!err); assert.ok(message1); - MessageBoxAPI.createMessage( - messageBoxId, - 'u:camtest:foo', - 'alfa', - {}, - (err, message2) => { + MessageBoxAPI.createMessage(messageBoxId, 'u:camtest:foo', 'alfa', {}, (err, message2) => { + assert.ok(!err); + assert.ok(message2); + MessageBoxAPI.getMessagesFromMessageBox(messageBoxId, null, null, null, (err, messages) => { assert.ok(!err); - assert.ok(message2); - MessageBoxAPI.getMessagesFromMessageBox( - messageBoxId, - null, - null, - null, - (err, messages) => { - assert.ok(!err); - verifyMessage(message1.id, 'alfa', null, messages); - verifyMessage(message2.id, 'alfa', null, messages); - return callback(); - } - ); - } - ); + verifyMessage(message1.id, 'alfa', null, messages); + verifyMessage(message2.id, 'alfa', null, messages); + return callback(); + }); + }); }); }); }); @@ -691,93 +564,71 @@ describe('Messagebox', () => { MessageBoxAPI.createMessage(messageBoxId, 'u:camtest:foo', 'alfa', {}, (err, message1) => { assert.ok(!err); assert.ok(message1); - setTimeout( - MessageBoxAPI.createMessage, - 10, - messageBoxId, - 'u:camtest:foo', - 'beta', - {}, - (err, message2) => { + setTimeout(MessageBoxAPI.createMessage, 10, messageBoxId, 'u:camtest:foo', 'beta', {}, (err, message2) => { + assert.ok(!err); + assert.ok(message2); + setTimeout(MessageBoxAPI.createMessage, 10, messageBoxId, 'u:camtest:foo', 'charly', {}, (err, message3) => { assert.ok(!err); - assert.ok(message2); - setTimeout( - MessageBoxAPI.createMessage, - 10, - messageBoxId, - 'u:camtest:foo', - 'charly', - {}, - (err, message3) => { - assert.ok(!err); - assert.ok(message3); - // Sanity check that the three messages are there - MessageBoxAPI.getMessagesFromMessageBox( - messageBoxId, - null, - null, - null, - (err, messages) => { - assert.ok(!err); - verifyMessage(message1.id, 'alfa', null, messages); - verifyMessage(message2.id, 'beta', null, messages); - verifyMessage(message3.id, 'charly', null, messages); + assert.ok(message3); + // Sanity check that the three messages are there + MessageBoxAPI.getMessagesFromMessageBox(messageBoxId, null, null, null, (err, messages) => { + assert.ok(!err); + verifyMessage(message1.id, 'alfa', null, messages); + verifyMessage(message2.id, 'beta', null, messages); + verifyMessage(message3.id, 'charly', null, messages); - // Soft delete message2, this should remove the body - MessageBoxAPI.deleteMessage( - messageBoxId, - message2.created, - { deleteType: 'soft' }, - (err, deleteType, deletedMessage) => { - assert.ok(!err); - assert.strictEqual(deleteType, 'soft'); - assert.ok(deletedMessage.deleted); - assert.ok(!deletedMessage.body); + // Soft delete message2, this should remove the body + MessageBoxAPI.deleteMessage( + messageBoxId, + message2.created, + { deleteType: 'soft' }, + (err, deleteType, deletedMessage) => { + assert.ok(!err); + assert.strictEqual(deleteType, 'soft'); + assert.ok(deletedMessage.deleted); + assert.ok(!deletedMessage.body); - MessageBoxAPI.getMessagesFromMessageBox( - messageBoxId, - null, - null, - { scrubDeleted: true }, - (err, messages) => { - assert.ok(!err); - - // DeletedMessage's body should be null and it's deleted flag should be set to true. - const deletedMessage = _.find(messages, message => { - return message.id === message2.id; - }); - assert.ok(deletedMessage.deleted); - assert.ok(!deletedMessage.body); - - // The other messages should still be there though. - verifyMessage(message1.id, 'alfa', null, messages); - verifyMessage(message3.id, 'charly', null, messages); - - // Sanity check that using no scrubDeleted flag returns the message. - MessageBoxAPI.getMessagesFromMessageBox( - messageBoxId, - null, - null, - { scrubDeleted: false }, - (err, messages) => { - assert.ok(!err); - - verifyMessage(message1.id, 'alfa', null, messages); - verifyMessage(message2.id, 'beta', null, messages); - verifyMessage(message3.id, 'charly', null, messages); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + MessageBoxAPI.getMessagesFromMessageBox( + messageBoxId, + null, + null, + { scrubDeleted: true }, + (err, messages) => { + assert.ok(!err); + + // DeletedMessage's body should be null and it's deleted flag should be set to true. + const deletedMessage = _.find(messages, message => { + return message.id === message2.id; + }); + assert.ok(deletedMessage.deleted); + assert.ok(!deletedMessage.body); + + // The other messages should still be there though. + verifyMessage(message1.id, 'alfa', null, messages); + verifyMessage(message3.id, 'charly', null, messages); + + // Sanity check that using no scrubDeleted flag returns the message. + MessageBoxAPI.getMessagesFromMessageBox( + messageBoxId, + null, + null, + { scrubDeleted: false }, + (err, messages) => { + assert.ok(!err); + + verifyMessage(message1.id, 'alfa', null, messages); + verifyMessage(message2.id, 'beta', null, messages); + verifyMessage(message3.id, 'charly', null, messages); + return callback(); + } + ); + } + ); + } + ); + }); + }); + }); }); }); }); @@ -798,48 +649,28 @@ describe('Messagebox', () => { MessageBoxAPI.deleteMessage(messageBoxId, null, {}, (err, deleteType, message) => { assert.strictEqual(err.code, 400); assert.ok(!message); - MessageBoxAPI.deleteMessage( - messageBoxId, - 'not a timestamp', - {}, - (err, deleteType, message) => { + MessageBoxAPI.deleteMessage(messageBoxId, 'not a timestamp', {}, (err, deleteType, message) => { + assert.strictEqual(err.code, 400); + assert.ok(!message); + MessageBoxAPI.deleteMessage(messageBoxId, Date.now() + 1000, {}, (err, deleteType, message) => { assert.strictEqual(err.code, 400); assert.ok(!message); - MessageBoxAPI.deleteMessage( - messageBoxId, - Date.now() + 1000, - {}, - (err, deleteType, message) => { - assert.strictEqual(err.code, 400); - assert.ok(!message); - // Invalid delete type. - MessageBoxAPI.deleteMessage( - messageBoxId, - null, - { deleteType: 'invalid' }, - (err, deleteType, message) => { - assert.strictEqual(err.code, 400); - assert.ok(!message); + // Invalid delete type. + MessageBoxAPI.deleteMessage(messageBoxId, null, { deleteType: 'invalid' }, (err, deleteType, message) => { + assert.strictEqual(err.code, 400); + assert.ok(!message); - // Non-existing message. - MessageBoxAPI.deleteMessage( - messageBoxId, - Date.now() - 1000, - {}, - (err, deleteType, message) => { - assert.strictEqual(err.code, 404, JSON.stringify(err, null, 4)); - assert.ok(!message); + // Non-existing message. + MessageBoxAPI.deleteMessage(messageBoxId, Date.now() - 1000, {}, (err, deleteType, message) => { + assert.strictEqual(err.code, 404, JSON.stringify(err, null, 4)); + assert.ok(!message); - return callback(); - } - ); - } - ); - } - ); - } - ); + return callback(); + }); + }); + }); + }); }); }); }); @@ -861,25 +692,19 @@ describe('Messagebox', () => { assert.strictEqual(deleteType, 'hard'); assert.ok(!message); - MessageBoxAPI.getMessagesFromMessageBox( - messageBoxId, - null, - null, - {}, - (err, messages) => { - assert.ok(!err); + MessageBoxAPI.getMessagesFromMessageBox(messageBoxId, null, null, {}, (err, messages) => { + assert.ok(!err); - // The tree originally has 6 messages, after hard deleting a leaf, it should have 5. - assert.strictEqual(messages.length, 5); + // The tree originally has 6 messages, after hard deleting a leaf, it should have 5. + assert.strictEqual(messages.length, 5); - // Make sure the correct message was deleted. - const a3Message = _.find(messages, message => { - return message.body === 'A3'; - }); - assert.ok(!a3Message); - return callback(); - } - ); + // Make sure the correct message was deleted. + const a3Message = _.find(messages, message => { + return message.body === 'A3'; + }); + assert.ok(!a3Message); + return callback(); + }); } ); }); @@ -904,22 +729,16 @@ describe('Messagebox', () => { assert.ok(!message.body); assert.ok(!message.createdBy); - MessageBoxAPI.getMessagesFromMessageBox( - messageBoxId, - null, - null, - {}, - (err, messages) => { - assert.ok(!err); + MessageBoxAPI.getMessagesFromMessageBox(messageBoxId, null, null, {}, (err, messages) => { + assert.ok(!err); - // The tree originally has 6 messages, after soft deleting a leaf, it should still have 6. - assert.strictEqual(messages.length, 6); + // The tree originally has 6 messages, after soft deleting a leaf, it should still have 6. + assert.strictEqual(messages.length, 6); - // Make sure the correct message was deleted. - verifyMessage(tree.a2.id, null, tree.a1.created, messages); - return callback(); - } - ); + // Make sure the correct message was deleted. + verifyMessage(tree.a2.id, null, tree.a1.created, messages); + return callback(); + }); } ); }); diff --git a/packages/oae-preview-processor/config/config.js b/packages/oae-preview-processor/config/config.js index cc71540724..7d81ce8bc6 100644 --- a/packages/oae-preview-processor/config/config.js +++ b/packages/oae-preview-processor/config/config.js @@ -13,51 +13,49 @@ * permissions and limitations under the License. */ -const Fields = require('oae-config/lib/fields'); +import * as Fields from 'oae-config/lib/fields'; -module.exports = { - title: 'OAE Preview Processor Module', - slideshare: { - name: 'SlideShare configuration', - description: 'Configuration for the SlideShare retriever', - elements: { - apikey: new Fields.Text('API Key', 'The SlideShare API key', '', { - tenantOverride: false, - suppress: true, - globalAdminOnly: true - }), - sharedsecret: new Fields.Text('Shared Secret', 'The SlideShare shared secret', '', { - tenantOverride: false, - suppress: true, - globalAdminOnly: true - }) - } - }, - flickr: { - name: 'Flickr configuration', - description: 'Configuration for the Flickr retriever', - elements: { - apikey: new Fields.Text('API Key', 'The Flickr API key', '', { - tenantOverride: false, - suppress: true, - globalAdminOnly: true - }), - apisecret: new Fields.Text('API Secret', 'The Flickr API secret', '', { - tenantOverride: false, - suppress: true, - globalAdminOnly: true - }) - } - }, - youtube: { - name: 'YouTube configuration', - description: 'Configuration for the YouTube retriever', - elements: { - key: new Fields.Text('Key', 'The YouTube server key', '', { - tenantOverride: false, - suppress: true, - globalAdminOnly: true - }) - } +export const title = 'OAE Preview Processor Module'; +export const slideshare = { + name: 'SlideShare configuration', + description: 'Configuration for the SlideShare retriever', + elements: { + apikey: new Fields.Text('API Key', 'The SlideShare API key', '', { + tenantOverride: false, + suppress: true, + globalAdminOnly: true + }), + sharedsecret: new Fields.Text('Shared Secret', 'The SlideShare shared secret', '', { + tenantOverride: false, + suppress: true, + globalAdminOnly: true + }) + } +}; +export const flickr = { + name: 'Flickr configuration', + description: 'Configuration for the Flickr retriever', + elements: { + apikey: new Fields.Text('API Key', 'The Flickr API key', '', { + tenantOverride: false, + suppress: true, + globalAdminOnly: true + }), + apisecret: new Fields.Text('API Secret', 'The Flickr API secret', '', { + tenantOverride: false, + suppress: true, + globalAdminOnly: true + }) + } +}; +export const youtube = { + name: 'YouTube configuration', + description: 'Configuration for the YouTube retriever', + elements: { + key: new Fields.Text('Key', 'The YouTube server key', '', { + tenantOverride: false, + suppress: true, + globalAdminOnly: true + }) } }; diff --git a/packages/oae-preview-processor/lib/activity.js b/packages/oae-preview-processor/lib/activity.js index cb3ccef19e..4b9eb3ada6 100644 --- a/packages/oae-preview-processor/lib/activity.js +++ b/packages/oae-preview-processor/lib/activity.js @@ -13,40 +13,37 @@ * permissions and limitations under the License. */ -const ActivityAPI = require('oae-activity'); -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityModel = require('oae-activity/lib/model'); -const { Context } = require('oae-context'); +import * as ActivityAPI from 'oae-activity'; +import * as ActivityModel from 'oae-activity/lib/model'; +import * as PreviewProcessorAPI from 'oae-preview-processor'; -const PreviewProcessorAPI = require('oae-preview-processor'); -const PreviewConstants = require('./constants'); +import { Context } from 'oae-context'; +import { ActivityConstants } from 'oae-activity/lib/constants'; +import PreviewConstants from './constants'; -PreviewProcessorAPI.emitter.on( - PreviewConstants.EVENTS.PREVIEWS_FINISHED, - (content, revision, status) => { - // Add the previews status. - // The actual images will be added by the content activity entity transformer - content.previews = content.previews || {}; - content.previews.status = status; +PreviewProcessorAPI.emitter.on(PreviewConstants.EVENTS.PREVIEWS_FINISHED, (content, revision, status) => { + // Add the previews status. + // The actual images will be added by the content activity entity transformer + content.previews = content.previews || {}; + content.previews.status = status; - const millis = Date.now(); - const actorResource = new ActivityModel.ActivitySeedResource('system', 'system', null); - const objectResource = new ActivityModel.ActivitySeedResource('content', content.id, { - content - }); - const activitySeed = new ActivityModel.ActivitySeed( - PreviewConstants.EVENTS.PREVIEWS_FINISHED, - millis, - ActivityConstants.verbs.CREATE, - actorResource, - objectResource - ); + const millis = Date.now(); + const actorResource = new ActivityModel.ActivitySeedResource('system', 'system', null); + const objectResource = new ActivityModel.ActivitySeedResource('content', content.id, { + content + }); + const activitySeed = new ActivityModel.ActivitySeed( + PreviewConstants.EVENTS.PREVIEWS_FINISHED, + millis, + ActivityConstants.verbs.CREATE, + actorResource, + objectResource + ); - // Fake a request context - const ctx = new Context(content.tenant); - ActivityAPI.postActivity(ctx, activitySeed); - } -); + // Fake a request context + const ctx = new Context(content.tenant); + ActivityAPI.postActivity(ctx, activitySeed); +}); ActivityAPI.registerActivityType(PreviewConstants.EVENTS.PREVIEWS_FINISHED, { groupBy: [{ actor: true }], diff --git a/packages/oae-preview-processor/lib/api.js b/packages/oae-preview-processor/lib/api.js index 9cf5eba03d..416d602d56 100644 --- a/packages/oae-preview-processor/lib/api.js +++ b/packages/oae-preview-processor/lib/api.js @@ -13,34 +13,36 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const ContentDAO = require('oae-content/lib/internal/dao'); -const EmitterAPI = require('oae-emitter'); -const log = require('oae-logger').logger('oae-preview-processor'); -const RestUtil = require('oae-rest/lib/util'); -const TaskQueue = require('oae-util/lib/taskqueue'); -const Telemetry = require('oae-telemetry').telemetry('preview-processor'); -const { Validator } = require('oae-util/lib/validator'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ContentDAO from 'oae-content/lib/internal/dao'; +import * as EmitterAPI from 'oae-emitter'; +import * as RestUtil from 'oae-rest/lib/util'; +import * as TaskQueue from 'oae-util/lib/taskqueue'; -// OAE Processors -const ImagesProcessor = require('oae-preview-processor/lib/processors/file/images'); -const OfficeProcessor = require('oae-preview-processor/lib/processors/file/office'); -const PDFProcessor = require('oae-preview-processor/lib/processors/file/pdf'); - -const DefaultLinkProcessor = require('oae-preview-processor/lib/processors/link/default'); -const FlickrLinkProcessor = require('oae-preview-processor/lib/processors/link/flickr'); -const SlideShareLinkProcessor = require('oae-preview-processor/lib/processors/link/slideshare'); -const VimeoLinkProcessor = require('oae-preview-processor/lib/processors/link/vimeo'); -const YoutubeLinkProcessor = require('oae-preview-processor/lib/processors/link/youtube'); - -const CollabDocProcessor = require('oae-preview-processor/lib/processors/collabdoc/collabdoc'); +import { telemetry } from 'oae-telemetry'; -const FolderProcessor = require('oae-preview-processor/lib/processors/folder'); -const { FilterGenerator } = require('./filters'); -const { PreviewContext } = require('./model'); -const PreviewConstants = require('./constants'); +// OAE Processors +import * as ImagesProcessor from 'oae-preview-processor/lib/processors/file/images'; +import * as OfficeProcessor from 'oae-preview-processor/lib/processors/file/office'; +import * as PDFProcessor from 'oae-preview-processor/lib/processors/file/pdf'; +import * as DefaultLinkProcessor from 'oae-preview-processor/lib/processors/link/default'; +import * as FlickrLinkProcessor from 'oae-preview-processor/lib/processors/link/flickr'; +import * as SlideShareLinkProcessor from 'oae-preview-processor/lib/processors/link/slideshare'; +import * as VimeoLinkProcessor from 'oae-preview-processor/lib/processors/link/vimeo'; +import * as YoutubeLinkProcessor from 'oae-preview-processor/lib/processors/link/youtube'; +import * as CollabDocProcessor from 'oae-preview-processor/lib/processors/collabdoc/collabdoc'; +import * as FolderProcessor from 'oae-preview-processor/lib/processors/folder'; + +import { logger } from 'oae-logger'; +import { Validator } from 'oae-util/lib/validator'; +import PreviewConstants from './constants'; +import { FilterGenerator } from './filters'; +import { PreviewContext } from './model'; + +const log = logger('oae-preview-processor'); +const Telemetry = telemetry('preview-processor'); let config = null; @@ -80,48 +82,38 @@ const enable = function(callback) { // Bind an error listener to the REST methods RestUtil.emitter.on('error', _restErrorLister); - TaskQueue.bind( - PreviewConstants.MQ.TASK_GENERATE_PREVIEWS, - _handleGeneratePreviewsTask, - options, - err => { - if (err) { - log().error({ err }, 'Could not bind to the generate previews queue'); - return callback(err); - } + TaskQueue.bind(PreviewConstants.MQ.TASK_GENERATE_PREVIEWS, _handleGeneratePreviewsTask, options, err => { + if (err) { + log().error({ err }, 'Could not bind to the generate previews queue'); + return callback(err); + } + + log().info('Bound the preview processor to the generate previews task queue'); - log().info('Bound the preview processor to the generate previews task queue'); + TaskQueue.bind( + PreviewConstants.MQ.TASK_GENERATE_FOLDER_PREVIEWS, + _handleGenerateFolderPreviewsTask, + options, + err => { + if (err) { + log().error({ err }, 'Could not bind to the generate folder previews queue'); + return callback(err); + } - TaskQueue.bind( - PreviewConstants.MQ.TASK_GENERATE_FOLDER_PREVIEWS, - _handleGenerateFolderPreviewsTask, - options, - err => { + log().info('Bound the preview processor to the generate folder previews task queue'); + + TaskQueue.bind(PreviewConstants.MQ.TASK_REGENERATE_PREVIEWS, _handleRegeneratePreviewsTask, null, err => { if (err) { - log().error({ err }, 'Could not bind to the generate folder previews queue'); + log().error({ err }, 'Could not bind to the regenerate previews queue'); return callback(err); } - log().info('Bound the preview processor to the generate folder previews task queue'); - - TaskQueue.bind( - PreviewConstants.MQ.TASK_REGENERATE_PREVIEWS, - _handleRegeneratePreviewsTask, - null, - err => { - if (err) { - log().error({ err }, 'Could not bind to the regenerate previews queue'); - return callback(err); - } - - log().info('Bound the preview processor to the regenerate previews task queue'); - return callback(); - } - ); - } - ); - } - ); + log().info('Bound the preview processor to the regenerate previews task queue'); + return callback(); + }); + } + ); + }); }; /** @@ -253,13 +245,13 @@ const registerProcessor = function(processorId, processor) { if (processorId) { validator.check(_processors[processorId], 'This processor is already registerd').isNull(); } + validator.check(null, 'Missing processor').isObject(processor); if (processor) { validator.check(processor.test, 'The processor has no test method').notNull(); - validator - .check(processor.generatePreviews, 'The processor has no generatePreviews method') - .notNull(); + validator.check(processor.generatePreviews, 'The processor has no generatePreviews method').notNull(); } + if (validator.hasErrors()) { throw new Error(validator.getFirstError()); } @@ -309,9 +301,11 @@ const getProcessor = function(ctx, contentObj, callback) { if (a.score < b.score) { return 1; } + if (a.score > b.score) { return -1; } + return 0; }); @@ -382,14 +376,11 @@ const reprocessPreviews = function(ctx, filters, callback) { validator .check(null, { code: 401, msg: 'Must be global administrator to reprocess previews' }) .isGlobalAdministratorUser(ctx); - validator - .check(null, { code: 400, msg: 'At least one filter must be specified' }) - .isObject(filters); + validator.check(null, { code: 400, msg: 'At least one filter must be specified' }).isObject(filters); if (_.isObject(filters)) { - validator - .check(_.keys(filters).length, { code: 400, msg: 'At least one filter must be specified' }) - .min(1); + validator.check(_.keys(filters).length, { code: 400, msg: 'At least one filter must be specified' }).min(1); } + if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -413,9 +404,7 @@ const reprocessPreview = function(ctx, contentId, revisionId, callback) { const validator = new Validator(); validator.check(contentId, { code: 400, msg: 'A content id must be provided' }).isResourceId(); validator.check(revisionId, { code: 400, msg: 'A revision id must be provided' }).isResourceId(); - validator - .check(null, { code: 401, msg: 'Must be logged in to reprocess previews' }) - .isLoggedInUser(ctx); + validator.check(null, { code: 401, msg: 'Must be logged in to reprocess previews' }).isLoggedInUser(ctx); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -458,8 +447,7 @@ const _handleGenerateFolderPreviewsTask = function(data, callback) { ); return callback({ code: 400, - msg: - 'An invalid generate folder previews task was submitted to the generate folder previews task queue' + msg: 'An invalid generate folder previews task was submitted to the generate folder previews task queue' }); } @@ -470,6 +458,7 @@ const _handleGenerateFolderPreviewsTask = function(data, callback) { Telemetry.incr('error.count'); return callback(err); } + // We're done. log().info({ folderId: data.folderId }, 'Folder preview processing done'); Telemetry.incr('ok.count'); @@ -498,10 +487,7 @@ const _handleGeneratePreviewsTask = function(data, callback) { }; if (!data.contentId) { - log().error( - { data }, - 'An invalid generate previews task was submitted to the generate previews task queue' - ); + log().error({ data }, 'An invalid generate previews task was submitted to the generate previews task queue'); return callback({ code: 400, msg: 'An invalid generate previews task was submitted to the generate previews task queue' @@ -533,17 +519,13 @@ const _handleGeneratePreviewsTask = function(data, callback) { _generatePreviews(ctx, err => { ctx.cleanup(); Telemetry.appendDuration('process.time', start); - PreviewProcessorAPI.emit( - PreviewConstants.EVENTS.PREVIEWS_FINISHED, - ctx.content, - ctx.revision, - ctx.getStatus() - ); + PreviewProcessorAPI.emit(PreviewConstants.EVENTS.PREVIEWS_FINISHED, ctx.content, ctx.revision, ctx.getStatus()); if (err) { log().error({ err, contentId: data.contentId }, 'Error when trying to process this file'); Telemetry.incr('error.count'); return callback(err); } + // We're done. log().info({ contentId: data.contentId }, 'Preview processing done'); Telemetry.incr('ok.count'); @@ -567,6 +549,7 @@ const _generatePreviews = function(ctx, callback) { if (err) { return callback(err); } + if (processor) { processor.generatePreviews(ctx, ctx.content, (err, ignored) => { if (err) { @@ -606,10 +589,7 @@ const _handleRegeneratePreviewsTask = function(data, callback) { }; if (!data.filters) { - log().error( - { data }, - 'An invalid regenerate previews task was submitted to the regenerate previews task queue' - ); + log().error({ data }, 'An invalid regenerate previews task was submitted to the regenerate previews task queue'); return callback({ code: 400, msg: 'An invalid regenerate previews task was submitted to the regenerate previews task queue' @@ -645,10 +625,7 @@ const _handleRegeneratePreviewsTask = function(data, callback) { * @api private */ const _onEach = function(contentRows, done) { - log().info( - 'Scanning %d content items to see if previews need to be reprocessed', - contentRows.length - ); + log().info('Scanning %d content items to see if previews need to be reprocessed', contentRows.length); totalScanned += contentRows.length; // Get those rows we can use to filter upon @@ -659,10 +636,7 @@ const _handleRegeneratePreviewsTask = function(data, callback) { return true; } catch (error) { // If the preview is invalid JSON, something bad happened. Lets try and reprocess it so the processor can better set the preview data - log().warn( - { contentRow }, - 'Found invalid JSON for content item. Forcing regeneration of previews' - ); + log().warn({ contentRow }, 'Found invalid JSON for content item. Forcing regeneration of previews'); } } else { // If there is no previews object, something is wrong. Try and reprocess it and reset it @@ -691,6 +665,7 @@ const _handleRegeneratePreviewsTask = function(data, callback) { }); return done(); } + // We need to filter by revisions const contentIds = _.map(filteredContent, contentObj => { return contentObj.contentId; @@ -795,8 +770,8 @@ const _registerDefaultProcessors = function() { registerProcessor('oae-collabdoc', CollabDocProcessor); }; -module.exports = { - emitter: PreviewProcessorAPI, +export { + PreviewProcessorAPI as emitter, enable, disable, refreshPreviewConfiguration, diff --git a/packages/oae-preview-processor/lib/constants.js b/packages/oae-preview-processor/lib/constants.js index ce37bd0076..437fb06dfc 100644 --- a/packages/oae-preview-processor/lib/constants.js +++ b/packages/oae-preview-processor/lib/constants.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -module.exports = { +export default { TYPES: { IMAGE: [ 'application/dicom', diff --git a/packages/oae-preview-processor/lib/filters.js b/packages/oae-preview-processor/lib/filters.js index 0ae8cf93c3..b9b10da524 100644 --- a/packages/oae-preview-processor/lib/filters.js +++ b/packages/oae-preview-processor/lib/filters.js @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const OaeUtil = require('oae-util/lib/util'); +import _ from 'underscore'; +import * as OaeUtil from 'oae-util/lib/util'; /** * Allows for validation and applying of filters when triggering a "reprocessing" previews task @@ -72,6 +72,7 @@ const FilterGenerator = function(filters) { if (content.previews && content.previews.status) { return _.contains(statuses, content.previews.status); } + // If the previews object is missing, something is seriously wrong and we should reprocess it return true; }); @@ -112,6 +113,7 @@ const FilterGenerator = function(filters) { if (revision.previews && revision.previews.status) { return _.contains(statuses, revision.previews.status); } + // If the previews object is missing, something is seriously wrong and we should reprocess it return true; }); @@ -230,6 +232,4 @@ const FilterGenerator = function(filters) { return that; }; -module.exports = { - FilterGenerator -}; +export { FilterGenerator }; diff --git a/packages/oae-preview-processor/lib/init.js b/packages/oae-preview-processor/lib/init.js index 64ba53e58d..c38be381b0 100644 --- a/packages/oae-preview-processor/lib/init.js +++ b/packages/oae-preview-processor/lib/init.js @@ -13,19 +13,21 @@ * permissions and limitations under the License. */ -const mkdirp = require('mkdirp'); +import mkdirp from 'mkdirp'; -const log = require('oae-logger').logger('oae-preview-processor'); -const Cleaner = require('oae-util/lib/cleaner'); +import { logger } from 'oae-logger'; -const PreviewAPI = require('./api'); +import * as Cleaner from 'oae-util/lib/cleaner'; +import * as PreviewAPI from './api'; // eslint-disable-next-line no-unused-vars -const activity = require('./activity'); +import * as activity from './activity'; + +const log = logger('oae-preview-processor'); /** * Starts listening for new pieces of content that should be handled. */ -module.exports = function(config, callback) { +export function init(config, callback) { // Create the previews directory and periodically clean it. // mkdirp does not throw an error if the directory already exist // so there is no need to check that first. @@ -40,4 +42,4 @@ module.exports = function(config, callback) { }); PreviewAPI.refreshPreviewConfiguration(config, callback); -}; +} diff --git a/packages/oae-preview-processor/lib/interface.js b/packages/oae-preview-processor/lib/interface.js index aa5c153e58..b0f6ad6ee1 100644 --- a/packages/oae-preview-processor/lib/interface.js +++ b/packages/oae-preview-processor/lib/interface.js @@ -39,7 +39,4 @@ const test = function(ctx, contentObj, callback) {}; */ const generatePreviews = function(ctx, contentObj, callback) {}; -module.exports = { - generatePreviews, - test -}; +export { generatePreviews, test }; diff --git a/packages/oae-preview-processor/lib/internal/puppeteer.js b/packages/oae-preview-processor/lib/internal/puppeteer.js index f9c23acbac..50df809d92 100644 --- a/packages/oae-preview-processor/lib/internal/puppeteer.js +++ b/packages/oae-preview-processor/lib/internal/puppeteer.js @@ -14,11 +14,13 @@ */ /* eslint-disable security/detect-non-literal-fs-filename */ -const fs = require('fs'); -const path = require('path'); -const puppeteer = require('puppeteer'); +import fs from 'fs'; +import path from 'path'; +import puppeteer from 'puppeteer'; -const log = require('oae-logger').logger('oae-preview-processor'); +import { logger } from 'oae-logger'; + +const log = logger('oae-preview-processor'); const launchOptions = { args: ['--disable-dev-shm-usage'] @@ -94,7 +96,7 @@ const getPuppeteerImage = function(url, imgPath, options, callback) { await navigateToPageIfUrl(page, url); await page.screenshot({ path: imgPath }); - } catch (ex) { + } catch (error) { if (isAttachment) { // Set image path to be blank in the case of attachment // webshot would write that file before, but not puppeteer @@ -110,8 +112,8 @@ const getPuppeteerImage = function(url, imgPath, options, callback) { } }); } else { - log().error({ err: ex }, 'Could not generate a screenshot.'); - return callback({ code: 500, msg: ex }); + log().error({ err: error }, 'Could not generate a screenshot.'); + return callback({ code: 500, msg: error }); } } finally { await browser.close(); @@ -120,4 +122,4 @@ const getPuppeteerImage = function(url, imgPath, options, callback) { })(); }; -module.exports = { getImage: getPuppeteerImage }; +export { getPuppeteerImage as getImage }; diff --git a/packages/oae-preview-processor/lib/model.js b/packages/oae-preview-processor/lib/model.js index 10307da9a8..4030a2d9f0 100644 --- a/packages/oae-preview-processor/lib/model.js +++ b/packages/oae-preview-processor/lib/model.js @@ -13,16 +13,18 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const path = require('path'); -const url = require('url'); -const util = require('util'); -const mkdirp = require('mkdirp'); -const rimraf = require('rimraf'); +import fs from 'fs'; +import path from 'path'; +import url from 'url'; +import util from 'util'; +import mkdirp from 'mkdirp'; +import rimraf from 'rimraf'; -const log = require('oae-logger').logger('oae-preview-processor'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); +import { logger } from 'oae-logger'; +import * as RestAPI from 'oae-rest'; +import { RestContext } from 'oae-rest/lib/model'; + +const log = logger('oae-preview-processor'); const extensionRegex = /^[a-zA-Z]+$/; @@ -106,51 +108,47 @@ const PreviewContext = function(config, contentId, revisionId) { log().trace({ contentId }, 'Logging into %s', tenantAlias); // Log in via signed auth, and get a new RestContext - RestAPI.Admin.getSignedTenantAuthenticationRequestInfo( - globalRestContext, - tenantAlias, - (err, requestInfo) => { + RestAPI.Admin.getSignedTenantAuthenticationRequestInfo(globalRestContext, tenantAlias, (err, requestInfo) => { + if (err) { + log().error( + { err, contentId }, + 'We could not get signed authentication request info for the tenant. The status of the content item will not be set' + ); + return callback(err); + } + + // Parse the URL we should use to authenticate to the tenant + const parsedUrl = url.parse(requestInfo.url); + + // We need to try and use the internally configured host rather than using the external host, + // so we extract the Host header portion from the suggested URI and replace the connection URI + // with the internal host + const { protocol } = parsedUrl; + const hostHeader = parsedUrl.host; + + // Use internal address if configured + const host = config.servers.serverInternalAddress || hostHeader; + const restCtx = new RestContext(util.format('%s//%s', protocol, host), { + hostHeader, + strictSSL + }); + + // Perform the actual login + // eslint-disable-next-line no-unused-vars + RestAPI.Admin.doSignedAuthentication(restCtx, requestInfo.body, (err, body, response) => { if (err) { log().error( { err, contentId }, - 'We could not get signed authentication request info for the tenant. The status of the content item will not be set' + 'We could not log in on the tenant. The status of the content item will not be set' ); return callback(err); } - // Parse the URL we should use to authenticate to the tenant - const parsedUrl = url.parse(requestInfo.url); - - // We need to try and use the internally configured host rather than using the external host, - // so we extract the Host header portion from the suggested URI and replace the connection URI - // with the internal host - const { protocol } = parsedUrl; - const hostHeader = parsedUrl.host; - - // Use internal address if configured - const host = config.servers.serverInternalAddress || hostHeader; - const restCtx = new RestContext(util.format('%s//%s', protocol, host), { - hostHeader, - strictSSL - }); - - // Perform the actual login - // eslint-disable-next-line no-unused-vars - RestAPI.Admin.doSignedAuthentication(restCtx, requestInfo.body, (err, body, response) => { - if (err) { - log().error( - { err, contentId }, - 'We could not log in on the tenant. The status of the content item will not be set' - ); - return callback(err); - } - - // Use this context for subsequent requests to the tenant - that.tenantRestContext = restCtx; - return callback(); - }); - } - ); + // Use this context for subsequent requests to the tenant + that.tenantRestContext = restCtx; + return callback(); + }); + }); }; /** @@ -170,22 +168,17 @@ const PreviewContext = function(config, contentId, revisionId) { // Stick the piece of content on the context. that.content = content; - RestAPI.Content.getRevision( - that.tenantRestContext, - contentId, - revisionId, - (err, revision) => { - if (err) { - log().error({ err, contentId, revisionId }, 'Could not get the revision'); - return callback(err); - } + RestAPI.Content.getRevision(that.tenantRestContext, contentId, revisionId, (err, revision) => { + if (err) { + log().error({ err, contentId, revisionId }, 'Could not get the revision'); + return callback(err); + } - // Stick the revision on the context. - that.revision = revision; + // Stick the revision on the context. + that.revision = revision; - callback(); - } - ); + callback(); + }); }); }; @@ -217,11 +210,9 @@ const PreviewContext = function(config, contentId, revisionId) { log().error({ err, contentId }, 'Error trying to download the file'); fs.unlink(path, unlinkErr => { if (unlinkErr) { - log().error( - { err: unlinkErr, contentId }, - 'Could not remove the downloaded file on download error' - ); + log().error({ err: unlinkErr, contentId }, 'Could not remove the downloaded file on download error'); } + callback(err); }); } else { @@ -314,6 +305,7 @@ const PreviewContext = function(config, contentId, revisionId) { files['thumbnail.png'] = function() { return fs.createReadStream(_thumbnailPath); }; + sizes['thumbnail.png'] = 'thumbnail'; } @@ -330,6 +322,7 @@ const PreviewContext = function(config, contentId, revisionId) { filename = path.basename(preview.path); files[filename] = preview.path; } + sizes[filename] = preview.size; }); @@ -356,17 +349,7 @@ const PreviewContext = function(config, contentId, revisionId) { that.setStatus = function(status, callback) { log().trace({ contentId }, 'Setting status to %s', status); _status = status; - RestAPI.Content.setPreviewItems( - that.tenantRestContext, - contentId, - revisionId, - status, - {}, - {}, - {}, - {}, - callback - ); + RestAPI.Content.setPreviewItems(that.tenantRestContext, contentId, revisionId, status, {}, {}, {}, {}, callback); }; /** @@ -381,6 +364,4 @@ const PreviewContext = function(config, contentId, revisionId) { return that; }; -module.exports = { - PreviewContext -}; +export { PreviewContext }; diff --git a/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js b/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js index 33a0539110..17172bd673 100644 --- a/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js +++ b/packages/oae-preview-processor/lib/processors/collabdoc/collabdoc.js @@ -13,21 +13,22 @@ * permissions and limitations under the License. */ -const { isResourceACollabDoc, isResourceACollabSheet } = require('oae-content/lib/backends/util'); +import fs from 'fs'; +import Path from 'path'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; +import _ from 'underscore'; +import cheerio from 'cheerio'; +import { isResourceACollabDoc, isResourceACollabSheet } from 'oae-content/lib/backends/util'; +import { logger } from 'oae-logger'; -const fs = require('fs'); -const Path = require('path'); -const _ = require('underscore'); -const cheerio = require('cheerio'); +import * as puppeteerHelper from 'oae-preview-processor/lib/internal/puppeteer'; -const ImageUtil = require('oae-util/lib/image'); -const IO = require('oae-util/lib/io'); -const log = require('oae-logger').logger('oae-preview-processor'); -const RestAPI = require('oae-rest'); -const OaeUtil = require('oae-util/lib/util'); +import * as ImageUtil from 'oae-util/lib/image'; +import * as IO from 'oae-util/lib/io'; +import * as RestAPI from 'oae-rest'; +import * as OaeUtil from 'oae-util/lib/util'; -const PreviewConstants = require('oae-preview-processor/lib/constants'); -const puppeteerHelper = require('oae-preview-processor/lib/internal/puppeteer'); +const log = logger('oae-preview-processor'); const screenShottingOptions = { viewport: { @@ -35,7 +36,6 @@ const screenShottingOptions = { height: PreviewConstants.SIZES.IMAGE.WIDE_HEIGHT } }; -const COLLABDOC = 'collabdoc'; /** * Initializes the CollabDocProcessor @@ -239,8 +239,4 @@ const _getWrappedCollabHtml = function(ctx, collabHtml, callback) { } }; -module.exports = { - init, - test, - generatePreviews -}; +export { init, test, generatePreviews }; diff --git a/packages/oae-preview-processor/lib/processors/file/images.js b/packages/oae-preview-processor/lib/processors/file/images.js index 4e4a50159d..a592104820 100644 --- a/packages/oae-preview-processor/lib/processors/file/images.js +++ b/packages/oae-preview-processor/lib/processors/file/images.js @@ -13,17 +13,14 @@ * permissions and limitations under the License. */ -const PreviewConstants = require('oae-preview-processor/lib/constants'); -const PreviewUtil = require('oae-preview-processor/lib/util'); +import PreviewConstants from 'oae-preview-processor/lib/constants'; +import * as PreviewUtil from 'oae-preview-processor/lib/util'; /** * @borrows Interface.test as Images.test */ const test = function(ctx, contentObj, callback) { - if ( - contentObj.resourceSubType === 'file' && - PreviewConstants.TYPES.IMAGE.indexOf(ctx.revision.mime) !== -1 - ) { + if (contentObj.resourceSubType === 'file' && PreviewConstants.TYPES.IMAGE.indexOf(ctx.revision.mime) !== -1) { callback(null, 10); } else { callback(null, -1); @@ -44,7 +41,4 @@ const generatePreviews = function(ctx, contentObj, callback) { }); }; -module.exports = { - test, - generatePreviews -}; +export { test, generatePreviews }; diff --git a/packages/oae-preview-processor/lib/processors/file/office.js b/packages/oae-preview-processor/lib/processors/file/office.js index 8fbe7c1569..ba75022daa 100644 --- a/packages/oae-preview-processor/lib/processors/file/office.js +++ b/packages/oae-preview-processor/lib/processors/file/office.js @@ -13,16 +13,18 @@ * permissions and limitations under the License. */ -const { exec } = require('child_process'); -const fs = require('fs'); -const Path = require('path'); -const util = require('util'); +import { exec } from 'child_process'; +import fs from 'fs'; +import Path from 'path'; +import util from 'util'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; -const log = require('oae-logger').logger('oae-preview-processor'); +import { logger } from 'oae-logger'; -const PDFProcessor = require('oae-preview-processor/lib/processors/file/pdf'); -const PreviewConstants = require('oae-preview-processor/lib/constants'); -const TempFile = require('oae-util/lib/tempfile'); +import * as PDFProcessor from 'oae-preview-processor/lib/processors/file/pdf'; +import * as TempFile from 'oae-util/lib/tempfile'; + +const log = logger('oae-preview-processor'); let _sofficeBinary = null; let _timeout = null; @@ -41,8 +43,7 @@ const init = function(config, callback) { if (!config || !config.binary || !config.timeout) { return callback({ code: 400, - msg: - 'Missing configuration for the Office Preview Processor, required fields are `binary` and `timeout`.' + msg: 'Missing configuration for the Office Preview Processor, required fields are `binary` and `timeout`.' }); } @@ -96,10 +97,7 @@ const init = function(config, callback) { * @borrows Interface.test as Office.test */ const test = function(ctx, contentObj, callback) { - if ( - contentObj.resourceSubType === 'file' && - PreviewConstants.TYPES.OFFICE.indexOf(ctx.revision.mime) !== -1 - ) { + if (contentObj.resourceSubType === 'file' && PreviewConstants.TYPES.OFFICE.indexOf(ctx.revision.mime) !== -1) { callback(null, 10); } else { callback(null, -1); @@ -150,10 +148,7 @@ const _convertToPdf = function(ctx, path, callback) { log().trace({ contentId: ctx.contentId }, 'Executing %s', cmd); exec(cmd, { timeout: _timeout }, (err, stdout, stderr) => { if (err) { - log().error( - { err, contentId: ctx.contentId, stdout, stderr }, - 'Could not convert the file to PDF.' - ); + log().error({ err, contentId: ctx.contentId, stdout, stderr }, 'Could not convert the file to PDF.'); return callback({ code: 500, msg: 'Could not convert the file to PDF.' }); } @@ -167,10 +162,7 @@ const _convertToPdf = function(ctx, path, callback) { // ex: http://askubuntu.com/questions/226295/libreoffice-command-line-conversion-no-output-file fs.stat(pdfPath, err => { if (err) { - log().error( - { contentId: ctx.contentId }, - 'Could not convert the file to PDF. Office failed silently' - ); + log().error({ contentId: ctx.contentId }, 'Could not convert the file to PDF. Office failed silently'); return callback({ code: 500, msg: 'Unable to convert the office file to pdf. Office failed silently.' @@ -182,8 +174,4 @@ const _convertToPdf = function(ctx, path, callback) { }); }; -module.exports = { - init, - test, - generatePreviews -}; +export { init, test, generatePreviews }; diff --git a/packages/oae-preview-processor/lib/processors/file/pdf.js b/packages/oae-preview-processor/lib/processors/file/pdf.js index fc48660d09..7cda46fd3f 100644 --- a/packages/oae-preview-processor/lib/processors/file/pdf.js +++ b/packages/oae-preview-processor/lib/processors/file/pdf.js @@ -13,24 +13,23 @@ * permissions and limitations under the License. */ +import fs from 'fs'; +import util from 'util'; +import path from 'path'; +import stream from 'stream'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; +import gm from 'gm'; +import pdfjsLib from 'pdfjs-dist'; +import _ from 'underscore'; +import { logger } from 'oae-logger'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as PreviewUtil from 'oae-preview-processor/lib/util'; import domStubs from './domstubs'; -const fs = require('fs'); -const util = require('util'); - const fsWriteFile = util.promisify(fs.writeFile); const fsMakeDir = util.promisify(fs.mkdir); -const path = require('path'); -const stream = require('stream'); -const gm = require('gm'); -const pdfjsLib = require('pdfjs-dist'); -const _ = require('underscore'); - -const log = require('oae-logger').logger('oae-preview-processor'); -const OaeUtil = require('oae-util/lib/util'); -const PreviewConstants = require('oae-preview-processor/lib/constants'); -const PreviewUtil = require('oae-preview-processor/lib/util'); +const log = logger('oae-preview-processor'); const PAGES_SUBDIRECTORY = 'pages'; const TXT_CONTENT_FILENAME = 'plain.txt'; @@ -282,9 +281,4 @@ const processAllPages = async function(ctx, pagesDir, numPages, doc) { } }; -module.exports = { - init, - test, - generatePreviews, - previewPDF -}; +export { init, test, generatePreviews, previewPDF }; diff --git a/packages/oae-preview-processor/lib/processors/folder/index.js b/packages/oae-preview-processor/lib/processors/folder/index.js index 48391a45fa..54d66ba9b9 100644 --- a/packages/oae-preview-processor/lib/processors/folder/index.js +++ b/packages/oae-preview-processor/lib/processors/folder/index.js @@ -13,24 +13,26 @@ * permissions and limitations under the License. */ -const { exec } = require('child_process'); -const fs = require('fs'); -const path = require('path'); -const util = require('util'); -const _ = require('underscore'); - -const { AuthzConstants } = require('oae-authz/lib/constants'); -const ContentUtil = require('oae-content/lib/internal/util'); -const { Context } = require('oae-context'); -const FoldersAPI = require('oae-folders'); -const { FoldersConstants } = require('oae-folders/lib/constants'); -const FoldersDAO = require('oae-folders/lib/internal/dao'); -const ImageUtil = require('oae-util/lib/image'); -const LibraryAPI = require('oae-library'); -const log = require('oae-logger').logger('folders-previews'); -const TempFile = require('oae-util/lib/tempfile'); - -const PreviewConstants = require('oae-preview-processor/lib/constants'); +import { exec } from 'child_process'; +import fs from 'fs'; +import path from 'path'; +import util from 'util'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; +import _ from 'underscore'; +import { logger } from 'oae-logger'; + +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { Context } from 'oae-context'; +import { FoldersConstants } from 'oae-folders/lib/constants'; + +import * as FoldersAPI from 'oae-folders'; +import * as ContentUtil from 'oae-content/lib/internal/util'; +import * as FoldersDAO from 'oae-folders/lib/internal/dao'; +import * as ImageUtil from 'oae-util/lib/image'; +import * as LibraryAPI from 'oae-library'; +import * as TempFile from 'oae-util/lib/tempfile'; + +const log = logger('folders-previews'); // The path to a file that only contains white pixels. This file can be used as // filler content in the generated image grid @@ -52,6 +54,7 @@ const generatePreviews = function(folderId, callback) { // However, we should set an empty previews object as we might have removed all the // content items that were used in the old thumbnail } + if (_.isEmpty(contentItems)) { return FoldersDAO.setPreviews(folder, {}, callback); } @@ -108,75 +111,67 @@ const _getData = function(folderId, callback) { const _getContentWithPreviews = function(folder, callback, _contentWithPreviews, _start) { _contentWithPreviews = _contentWithPreviews || []; - FoldersDAO.getContentItems( - folder.groupId, - { start: _start, limit: 20 }, - (err, contentItems, nextToken) => { - if (err) { - return callback(err); - } + FoldersDAO.getContentItems(folder.groupId, { start: _start, limit: 20 }, (err, contentItems, nextToken) => { + if (err) { + return callback(err); + } - _contentWithPreviews = _.chain(contentItems) - // Remove null content items. This can happen if libraries are in an inconsistent - // state. For example, if an item was deleted from the system but hasn't been removed - // from the libraries, a `null` value would be returned by `getMultipleContentItems` - .compact() - - // We can only use content items that have a thumbnail - .filter(contentItem => { - return contentItem.previews && contentItem.previews.thumbnailUri; - }) - - // Only use content items that are implicitly visible to those that can see this - // folder's library - .filter(contentItem => { - // If this content item were inserted into the folder's content library, it would be - // in this visibility bucket (e.g., a public content item would be in the public - // library) - const targetBucketVisibility = LibraryAPI.Authz.resolveLibraryBucketVisibility( - folder.id, - contentItem - ); - const targetBucketVisibilityPriority = AuthzConstants.visibility.ALL_PRIORITY.indexOf( - targetBucketVisibility - ); - - // If a user has access to see this visibility of folder implicitly, then they will - // get this visibility bucket. E.g., if folder is public, we only use content items - // from the public visibility bucket (i.e., public content items) - const implicitBucketVisibility = folder.visibility; - const implicitBucketVisibilityPriority = AuthzConstants.visibility.ALL_PRIORITY.indexOf( - implicitBucketVisibility - ); - - // Only use content items whose target bucket visibility is visibile within the - // implicit bucket visibility - return targetBucketVisibilityPriority <= implicitBucketVisibilityPriority; - }) - - // Add them to the set of items we've already retrieved - .concat(_contentWithPreviews) - - // Ensure that the newest items are on top. Underscore's sortBy function sorts ascending, - // so we multiply the lastModified timestamp with `-1` so the newest (=highest) value - // comes first - .sortBy(contentItem => { - return -1 * contentItem.lastModified; - }) - .value() - - // We only use up to 8 items, so only hold on to the 8 newest items - .slice(0, 8); - - if (!nextToken) { - // Once we have exhausted all items and kept only the 8 most recent, we return with our - // results - return callback(null, _contentWithPreviews); - } - // There are more to list, run recursively - return _getContentWithPreviews(folder, callback, _contentWithPreviews, nextToken); + _contentWithPreviews = _.chain(contentItems) + // Remove null content items. This can happen if libraries are in an inconsistent + // state. For example, if an item was deleted from the system but hasn't been removed + // from the libraries, a `null` value would be returned by `getMultipleContentItems` + .compact() + + // We can only use content items that have a thumbnail + .filter(contentItem => { + return contentItem.previews && contentItem.previews.thumbnailUri; + }) + + // Only use content items that are implicitly visible to those that can see this + // folder's library + .filter(contentItem => { + // If this content item were inserted into the folder's content library, it would be + // in this visibility bucket (e.g., a public content item would be in the public + // library) + const targetBucketVisibility = LibraryAPI.Authz.resolveLibraryBucketVisibility(folder.id, contentItem); + const targetBucketVisibilityPriority = AuthzConstants.visibility.ALL_PRIORITY.indexOf(targetBucketVisibility); + + // If a user has access to see this visibility of folder implicitly, then they will + // get this visibility bucket. E.g., if folder is public, we only use content items + // from the public visibility bucket (i.e., public content items) + const implicitBucketVisibility = folder.visibility; + const implicitBucketVisibilityPriority = AuthzConstants.visibility.ALL_PRIORITY.indexOf( + implicitBucketVisibility + ); + + // Only use content items whose target bucket visibility is visibile within the + // implicit bucket visibility + return targetBucketVisibilityPriority <= implicitBucketVisibilityPriority; + }) + + // Add them to the set of items we've already retrieved + .concat(_contentWithPreviews) + + // Ensure that the newest items are on top. Underscore's sortBy function sorts ascending, + // so we multiply the lastModified timestamp with `-1` so the newest (=highest) value + // comes first + .sortBy(contentItem => { + return -1 * contentItem.lastModified; + }) + .value() + + // We only use up to 8 items, so only hold on to the 8 newest items + .slice(0, 8); + + if (!nextToken) { + // Once we have exhausted all items and kept only the 8 most recent, we return with our + // results + return callback(null, _contentWithPreviews); } - ); + + // There are more to list, run recursively + return _getContentWithPreviews(folder, callback, _contentWithPreviews, nextToken); + }); }; /** @@ -403,6 +398,7 @@ const _removeOldPreviews = function(ctx, folder, callback) { if (err) { return callback(err); } + _removeOldPreview(ctx, folder, 'wideUri', callback); }); }; @@ -439,32 +435,20 @@ const _removeOldPreview = function(ctx, folder, type, callback) { const _storeNewPreviews = function(ctx, folder, thumbnail, wide, callback) { // Store the files with a unique filename let filename = util.format('thumbnail_%s.jpg', Date.now()); - ContentUtil.getStorageBackend(ctx).store( - ctx, - thumbnail, - { filename, resourceId: folder.id }, - (err, thumbnailUri) => { + ContentUtil.getStorageBackend(ctx).store(ctx, thumbnail, { filename, resourceId: folder.id }, (err, thumbnailUri) => { + if (err) { + return callback(err); + } + + filename = util.format('wide_%s.jpg', Date.now()); + ContentUtil.getStorageBackend(ctx).store(ctx, wide, { filename, resourceId: folder.id }, (err, wideUri) => { if (err) { return callback(err); } - filename = util.format('wide_%s.jpg', Date.now()); - ContentUtil.getStorageBackend(ctx).store( - ctx, - wide, - { filename, resourceId: folder.id }, - (err, wideUri) => { - if (err) { - return callback(err); - } - - return callback(null, thumbnailUri, wideUri); - } - ); - } - ); + return callback(null, thumbnailUri, wideUri); + }); + }); }; -module.exports = { - generatePreviews -}; +export { generatePreviews }; diff --git a/packages/oae-preview-processor/lib/processors/link/default.js b/packages/oae-preview-processor/lib/processors/link/default.js index c6551dbaf7..ca230ab83c 100644 --- a/packages/oae-preview-processor/lib/processors/link/default.js +++ b/packages/oae-preview-processor/lib/processors/link/default.js @@ -13,21 +13,24 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const path = require('path'); -const url = require('url'); -const _ = require('underscore'); -const gm = require('gm'); -const rangeCheck = require('range_check'); -const request = require('request'); - -const log = require('oae-logger').logger('oae-preview-processor'); -const OaeUtil = require('oae-util/lib/util'); -const PrincipalsConfig = require('oae-config').config('oae-principals'); - -const LinkProcessorUtil = require('oae-preview-processor/lib/processors/link/util'); -const PreviewConstants = require('oae-preview-processor/lib/constants'); -const puppeteerHelper = require('oae-preview-processor/lib/internal/puppeteer'); +import fs from 'fs'; +import path from 'path'; +import url from 'url'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; +import _ from 'underscore'; +import gm from 'gm'; +import rangeCheck from 'range_check'; +import request from 'request'; + +import { logger } from 'oae-logger'; + +import * as OaeUtil from 'oae-util/lib/util'; +import { setUpConfig } from 'oae-config'; +import * as LinkProcessorUtil from 'oae-preview-processor/lib/processors/link/util'; +import * as puppeteerHelper from 'oae-preview-processor/lib/internal/puppeteer'; + +const log = logger('oae-preview-processor'); +const PrincipalsConfig = setUpConfig('oae-principals'); const screenShottingOptions = {}; @@ -42,10 +45,7 @@ const screenShottingOptions = {}; const init = function(_config, callback) { _config = _config || {}; - screenShottingOptions.timeout = OaeUtil.getNumberParam( - _config.screenShotting.timeout, - screenShottingOptions.timeout - ); + screenShottingOptions.timeout = OaeUtil.getNumberParam(_config.screenShotting.timeout, screenShottingOptions.timeout); const chromiumExecutable = _config.screenShotting.binary; if (chromiumExecutable) { @@ -65,17 +65,13 @@ const test = function(ctx, contentObj, callback) { // Only allow HTTP(S) URLs if (/^http(s)?:\/\//.test(link)) { // Don't generate previews for internal IPs - if ( - !rangeCheck.inRange( - link.slice(link.lastIndexOf('://') + 1), - PreviewConstants.FORBIDDEN.INTERNAL_IPS - ) - ) { + if (!rangeCheck.inRange(link.slice(link.lastIndexOf('://') + 1), PreviewConstants.FORBIDDEN.INTERNAL_IPS)) { // Default to the lowest possible score return callback(null, 1); } } } + return callback(null, -1); }; @@ -93,11 +89,7 @@ const generatePreviews = function(ctx, contentObj, callback) { // Certain webservers will not send an `x-frame-options` header when no browser user agent is not specified 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36', - 'Accept-Language': PrincipalsConfig.getValue( - contentObj.tenant.alias, - 'user', - 'defaultLanguage' - ) + 'Accept-Language': PrincipalsConfig.getValue(contentObj.tenant.alias, 'user', 'defaultLanguage') } }; @@ -114,8 +106,7 @@ const generatePreviews = function(ctx, contentObj, callback) { response.headers['content-disposition'] && response.headers['content-disposition'].split(';')[0] === 'attachment'; // ..or it's type is 'application/octet-stream' - forcedDownload = - forcedDownload || response.headers['content-type'] === PreviewConstants.TYPES.DEFAULT; + forcedDownload = forcedDownload || response.headers['content-type'] === PreviewConstants.TYPES.DEFAULT; /* * See https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options @@ -157,11 +148,7 @@ const generatePreviews = function(ctx, contentObj, callback) { } else { // Try to localize the screenshot of the link to the default tenant language screenShottingOptions.customHeaders = { - 'Accept-Language': PrincipalsConfig.getValue( - contentObj.tenant.alias, - 'user', - 'defaultLanguage' - ) + 'Accept-Language': PrincipalsConfig.getValue(contentObj.tenant.alias, 'user', 'defaultLanguage') }; puppeteerHelper.getImage(contentObj.link, imgPath, screenShottingOptions, err => { if (err) { @@ -186,6 +173,7 @@ const generatePreviews = function(ctx, contentObj, callback) { log().info({ contentId: ctx.contentId }, 'Not attaching blank screenshot'); return callback(); } + return LinkProcessorUtil.generatePreviewsFromImage(ctx, imgPath, null, callback); } ); @@ -203,6 +191,7 @@ const generatePreviews = function(ctx, contentObj, callback) { // Otherwise we need to do an extra request } + urlParts.protocol = 'https:'; const link = url.format(urlParts); _checkHttps(link, httpsAccessible => { @@ -236,8 +225,4 @@ const _checkHttps = function(link, callback) { }); }; -module.exports = { - init, - test, - generatePreviews -}; +export { init, test, generatePreviews }; diff --git a/packages/oae-preview-processor/lib/processors/link/flickr.js b/packages/oae-preview-processor/lib/processors/link/flickr.js index 3336a97d9f..35af46c2ca 100644 --- a/packages/oae-preview-processor/lib/processors/link/flickr.js +++ b/packages/oae-preview-processor/lib/processors/link/flickr.js @@ -13,14 +13,17 @@ * permissions and limitations under the License. */ -const util = require('util'); -const request = require('request'); +import util from 'util'; +import request from 'request'; -const log = require('oae-logger').logger('oae-preview-processor'); -const PreviewConfig = require('oae-config').config('oae-preview-processor'); +import { logger } from 'oae-logger'; +import { setUpConfig } from 'oae-config'; -const LinkProcessorUtil = require('oae-preview-processor/lib/processors/link/util'); -const PreviewUtil = require('oae-preview-processor/lib/util'); +import * as LinkProcessorUtil from 'oae-preview-processor/lib/processors/link/util'; +import * as PreviewUtil from 'oae-preview-processor/lib/util'; + +const log = logger('oae-preview-processor'); +const PreviewConfig = setUpConfig('oae-preview-processor'); // A regular expression that can be used to check if a URL points to a specific photo const REGEX_PHOTO = /^http(s)?:\/\/(www\.)?flickr\.com\/photos\/([-_a-zA-Z0-9@]+)\/(\d+)/; @@ -69,13 +72,10 @@ const test = function(ctx, contentObj, callback) { } // Only allow URLs that are on the Flickr domain - if ( - REGEX_PHOTO.test(contentObj.link) || - REGEX_SHORT_PHOTO.test(contentObj.link) || - REGEX_SET.test(contentObj.link) - ) { + if (REGEX_PHOTO.test(contentObj.link) || REGEX_SHORT_PHOTO.test(contentObj.link) || REGEX_SET.test(contentObj.link)) { return callback(null, 10); } + return callback(null, -1); }; @@ -84,17 +84,18 @@ const test = function(ctx, contentObj, callback) { */ const generatePreviews = function(ctx, contentObj, callback) { /*! - * Downloads a thumbnail from flickr and processes it - * - * @param {Object} err An error object coming from the metadata fetchers - * @param {Object} opts The object with metadata that we can use to fetch the image and/or a displayname and a description - * @param {Boolean} ignore If this value is set to `true` we'll ignore the picture - * @api private - */ + * Downloads a thumbnail from flickr and processes it + * + * @param {Object} err An error object coming from the metadata fetchers + * @param {Object} opts The object with metadata that we can use to fetch the image and/or a displayname and a description + * @param {Boolean} ignore If this value is set to `true` we'll ignore the picture + * @api private + */ const handleDownload = function(err, opts, ignore) { if (err) { return callback(err); } + if (ignore) { return callback(null, true); } @@ -148,6 +149,7 @@ const _getFlickrPhoto = function(ctx, id, callback) { log().error({ err, body }, 'An unexpected error occurred getting a Flickr photo'); return callback(err); } + if (response.statusCode !== 200) { err = { code: response.statusCode, msg: body }; log().error({ err }, 'An unexpected error occurred getting a Flickr photo'); @@ -201,6 +203,7 @@ const _getFlickrSet = function(ctx, id, callback) { log().error({ err, body }, 'An unexpected error occurred getting a Flickr photo set'); return callback(err); } + if (response.statusCode !== 200) { err = { code: response.statusCode, msg: body }; log().error({ err }, 'An unexpected error occurred getting a Flickr photo set'); @@ -224,12 +227,7 @@ const _getFlickrSet = function(ctx, id, callback) { return callback(null, { displayName: info.photoset.title._content, description: info.photoset.description._content, - imageUrl: _getImageUrl( - info.photoset.farm, - info.photoset.server, - info.photoset.primary, - info.photoset.secret - ) + imageUrl: _getImageUrl(info.photoset.farm, info.photoset.server, info.photoset.primary, info.photoset.secret) }); }); }; @@ -274,6 +272,7 @@ const _getType = function(url) { if (match) { return { type: 'photo', id: match[4] }; } + match = url.match(REGEX_SHORT_PHOTO); if (match) { return { type: 'photo', id: _base58Decode(match[2]) }; @@ -327,9 +326,4 @@ const _base58Decode = function(s) { return val; }; -module.exports = { - setApiUrl, - setImageUrl, - test, - generatePreviews -}; +export { setApiUrl, setImageUrl, test, generatePreviews }; diff --git a/packages/oae-preview-processor/lib/processors/link/slideshare.js b/packages/oae-preview-processor/lib/processors/link/slideshare.js index 5b385f8577..8b3d3fbad3 100644 --- a/packages/oae-preview-processor/lib/processors/link/slideshare.js +++ b/packages/oae-preview-processor/lib/processors/link/slideshare.js @@ -13,13 +13,15 @@ * permissions and limitations under the License. */ -const slideshare = require('slideshare'); +import slideshare from 'slideshare'; +import { logger } from 'oae-logger'; +import { setUpConfig } from 'oae-config'; -const log = require('oae-logger').logger('oae-preview-processor'); -const PreviewConfig = require('oae-config').config('oae-preview-processor'); +import * as LinkProcessorUtil from 'oae-preview-processor/lib/processors/link/util'; +import * as PreviewUtil from 'oae-preview-processor/lib/util'; -const LinkProcessorUtil = require('oae-preview-processor/lib/processors/link/util'); -const PreviewUtil = require('oae-preview-processor/lib/util'); +const log = logger('oae-preview-processor'); +const PreviewConfig = setUpConfig('oae-preview-processor'); // Regular expression that will be used to check if the provided URL is a SlideShare URL const SLIDES_REGEX = /^http(s)?:\/\/(www\.)?slideshare\.net\/(\w+)\/(\w+)/; @@ -55,6 +57,7 @@ const test = function(ctx, contentObj, callback) { if (SLIDES_REGEX.test(contentObj.link)) { return callback(null, 10); } + return callback(null, -1); }; @@ -70,14 +73,12 @@ const generatePreviews = function(ctx, contentObj, callback) { ss.api_url = apiUrl; ss.getSlideshowByURL(contentObj.link, response => { if (!response || response.SlideShareServiceError) { - log().error( - { err: response.SlideShareServiceError }, - 'Failed to interact with the SlideShare API' - ); + log().error({ err: response.SlideShareServiceError }, 'Failed to interact with the SlideShare API'); return callback({ code: 500, msg: 'Failed to interact with the SlideShare API' }); // Ignore this image if it has no thumbnail } + if (!response.Slideshow || !response.Slideshow.ThumbnailURL) { return callback(null, true); } @@ -92,6 +93,7 @@ const generatePreviews = function(ctx, contentObj, callback) { if (result.Title && result.Title.length > 0) { opts.displayName = result.Title[0]; } + if (result.Description && result.Description.length > 0) { opts.description = result.Description[0]; } @@ -122,8 +124,4 @@ const _getConfig = function() { }; }; -module.exports = { - setApiURL, - test, - generatePreviews -}; +export { setApiURL, test, generatePreviews }; diff --git a/packages/oae-preview-processor/lib/processors/link/util.js b/packages/oae-preview-processor/lib/processors/link/util.js index 725579b921..6af898ada1 100644 --- a/packages/oae-preview-processor/lib/processors/link/util.js +++ b/packages/oae-preview-processor/lib/processors/link/util.js @@ -13,11 +13,12 @@ * permissions and limitations under the License. */ -const ent = require('ent'); +import ent from 'ent'; +import { logger } from 'oae-logger'; -const log = require('oae-logger').logger('oae-preview-processor'); +import * as PreviewUtil from 'oae-preview-processor/lib/util'; -const PreviewUtil = require('oae-preview-processor/lib/util'); +const log = logger('oae-preview-processor'); /** * Resizes an image generated by one of the link processors in the appropriate sizes and updates @@ -40,11 +41,7 @@ const generatePreviewsFromImage = function(ctx, path, opts, callback) { // Check if we can update the main content metadata (displayName, description, ..) opts = opts || {}; - if ( - opts.displayName && - ctx.content.displayName === ctx.content.link && - typeof opts.displayName === 'string' - ) { + if (opts.displayName && ctx.content.displayName === ctx.content.link && typeof opts.displayName === 'string') { ctx.addContentMetadata('displayName', ent.decode(opts.displayName)); log().trace({ contentId: ctx.contentId }, 'Updating the content displayName.'); } @@ -58,4 +55,4 @@ const generatePreviewsFromImage = function(ctx, path, opts, callback) { }); }; -module.exports = { generatePreviewsFromImage }; +export { generatePreviewsFromImage }; diff --git a/packages/oae-preview-processor/lib/processors/link/vimeo.js b/packages/oae-preview-processor/lib/processors/link/vimeo.js index 8314dac62f..0b3f0b2998 100644 --- a/packages/oae-preview-processor/lib/processors/link/vimeo.js +++ b/packages/oae-preview-processor/lib/processors/link/vimeo.js @@ -13,13 +13,13 @@ * permissions and limitations under the License. */ -const util = require('util'); -const path = require('path'); -const _ = require('underscore'); -const request = require('request'); +import util from 'util'; +import path from 'path'; +import _ from 'underscore'; +import request from 'request'; -const LinkProcessorUtil = require('oae-preview-processor/lib/processors/link/util'); -const PreviewUtil = require('oae-preview-processor/lib/util'); +import * as LinkProcessorUtil from 'oae-preview-processor/lib/processors/link/util'; +import * as PreviewUtil from 'oae-preview-processor/lib/util'; const VIMEO_REGEX = /^http(s)?:\/\/(www\.)?vimeo\.com\/(\d+)(\/.*)?$/; @@ -36,6 +36,7 @@ const test = function(ctx, contentObj, callback) { if (VIMEO_REGEX.test(contentObj.link)) { return callback(null, 10); } + return callback(null, -1); }; @@ -91,10 +92,8 @@ const _getId = function(url) { if (match) { return match[3]; } + return null; }; -module.exports = { - test, - generatePreviews -}; +export { test, generatePreviews }; diff --git a/packages/oae-preview-processor/lib/processors/link/youtube.js b/packages/oae-preview-processor/lib/processors/link/youtube.js index d1497f1f4e..598aa12eae 100644 --- a/packages/oae-preview-processor/lib/processors/link/youtube.js +++ b/packages/oae-preview-processor/lib/processors/link/youtube.js @@ -13,14 +13,16 @@ * permissions and limitations under the License. */ -const url = require('url'); -const Youtube = require('youtube-api'); +import url from 'url'; +import Youtube from 'youtube-api'; +import { logger } from 'oae-logger'; +import { setUpConfig } from 'oae-config'; -const log = require('oae-logger').logger('oae-preview-processor'); +import * as LinkProcessorUtil from 'oae-preview-processor/lib/processors/link/util'; +import * as PreviewUtil from 'oae-preview-processor/lib/util'; -const LinkProcessorUtil = require('oae-preview-processor/lib/processors/link/util'); -const PreviewConfig = require('oae-config').config('oae-preview-processor'); -const PreviewUtil = require('oae-preview-processor/lib/util'); +const log = logger('oae-preview-processor'); +const PreviewConfig = setUpConfig('oae-preview-processor'); const YOUTUBE_FULL_REGEX = /^http(s)?:\/\/(www\.)?youtube\.com\/watch/; const YOUTUBE_SHORT_REGEX = /^http(s)?:\/\/youtu.be\/(.+)/; @@ -44,6 +46,7 @@ const test = function(ctx, contentObj, callback) { if (YOUTUBE_FULL_REGEX.test(contentObj.link) || YOUTUBE_SHORT_REGEX.test(contentObj.link)) { return callback(null, 10); } + return callback(null, -1); }; @@ -109,15 +112,14 @@ const _getId = function(link) { // The short link has it as its path } + if (parsedUrl.hostname === 'youtu.be') { return parsedUrl.pathname.substr(1); // Although not really possible, but we return null in all other cases } + return null; }; -module.exports = { - test, - generatePreviews -}; +export { test, generatePreviews }; diff --git a/packages/oae-preview-processor/lib/rest.js b/packages/oae-preview-processor/lib/rest.js index 02fb1c46da..8c9271fd71 100644 --- a/packages/oae-preview-processor/lib/rest.js +++ b/packages/oae-preview-processor/lib/rest.js @@ -13,12 +13,12 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const urlExpander = require('expand-url'); +import _ from 'underscore'; +import urlExpander from 'expand-url'; -const OAE = require('oae-util/lib/oae'); +import * as OAE from 'oae-util/lib/oae'; -const PreviewProcessorAPI = require('oae-preview-processor'); +import * as PreviewProcessorAPI from 'oae-preview-processor'; /** * @REST postContentReprocessPreviews diff --git a/packages/oae-preview-processor/lib/test/util.js b/packages/oae-preview-processor/lib/test/util.js index cfe700681c..aba8c1f4f5 100644 --- a/packages/oae-preview-processor/lib/test/util.js +++ b/packages/oae-preview-processor/lib/test/util.js @@ -13,12 +13,11 @@ * permissions and limitations under the License. */ -const assert = require('assert'); +import assert from 'assert'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; -const MQ = require('oae-util/lib/mq'); -const TaskQueue = require('oae-util/lib/taskqueue'); - -const PreviewConstants = require('oae-preview-processor/lib/constants'); +import * as MQ from 'oae-util/lib/mq'; +import * as TaskQueue from 'oae-util/lib/taskqueue'; /** * Purges all the events out of the previews queue @@ -31,26 +30,21 @@ const purgePreviewsQueue = function(callback) { TaskQueue.unbind(PreviewConstants.MQ.TASK_GENERATE_PREVIEWS, err => { assert.ok(!err); - TaskQueue.bind( - PreviewConstants.MQ.TASK_GENERATE_PREVIEWS, - () => {}, - { subscribe: { subscribe: false } }, - err => { + TaskQueue.bind(PreviewConstants.MQ.TASK_GENERATE_PREVIEWS, () => {}, { subscribe: { subscribe: false } }, err => { + assert.ok(!err); + + // Purge anything that is in the queue + MQ.purge(PreviewConstants.MQ.TASK_GENERATE_PREVIEWS, err => { assert.ok(!err); - // Purge anything that is in the queue - MQ.purge(PreviewConstants.MQ.TASK_GENERATE_PREVIEWS, err => { + // Unbind our dummy-handler from the queue + TaskQueue.unbind(PreviewConstants.MQ.TASK_GENERATE_PREVIEWS, err => { assert.ok(!err); - // Unbind our dummy-handler from the queue - TaskQueue.unbind(PreviewConstants.MQ.TASK_GENERATE_PREVIEWS, err => { - assert.ok(!err); - - return callback(); - }); + return callback(); }); - } - ); + }); + }); }); }; @@ -65,26 +59,21 @@ const purgeRegeneratePreviewsQueue = function(callback) { TaskQueue.unbind(PreviewConstants.MQ.TASK_REGENERATE_PREVIEWS, err => { assert.ok(!err); - TaskQueue.bind( - PreviewConstants.MQ.TASK_REGENERATE_PREVIEWS, - () => {}, - { subscribe: { subscribe: false } }, - err => { + TaskQueue.bind(PreviewConstants.MQ.TASK_REGENERATE_PREVIEWS, () => {}, { subscribe: { subscribe: false } }, err => { + assert.ok(!err); + + // Purge anything that is in the queue + MQ.purge(PreviewConstants.MQ.TASK_REGENERATE_PREVIEWS, err => { assert.ok(!err); - // Purge anything that is in the queue - MQ.purge(PreviewConstants.MQ.TASK_REGENERATE_PREVIEWS, err => { + // Unbind our dummy-handler from the queue + TaskQueue.unbind(PreviewConstants.MQ.TASK_REGENERATE_PREVIEWS, err => { assert.ok(!err); - // Unbind our dummy-handler from the queue - TaskQueue.unbind(PreviewConstants.MQ.TASK_REGENERATE_PREVIEWS, err => { - assert.ok(!err); - - return callback(); - }); + return callback(); }); - } - ); + }); + }); }); }; @@ -122,8 +111,4 @@ const purgeFoldersPreviewsQueue = function(callback) { }); }; -module.exports = { - purgePreviewsQueue, - purgeRegeneratePreviewsQueue, - purgeFoldersPreviewsQueue -}; +export { purgePreviewsQueue, purgeRegeneratePreviewsQueue, purgeFoldersPreviewsQueue }; diff --git a/packages/oae-preview-processor/lib/util.js b/packages/oae-preview-processor/lib/util.js index fcdf290874..4fcd257ec7 100644 --- a/packages/oae-preview-processor/lib/util.js +++ b/packages/oae-preview-processor/lib/util.js @@ -13,17 +13,19 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const util = require('util'); -const gm = require('gm'); -const request = require('request'); -const _ = require('underscore'); +import fs from 'fs'; +import util from 'util'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; +import gm from 'gm'; +import request from 'request'; +import _ from 'underscore'; -const ImageUtil = require('oae-util/lib/image'); -const IO = require('oae-util/lib/io'); -const log = require('oae-logger').logger('oae-preview-processor'); +import * as ImageUtil from 'oae-util/lib/image'; +import * as IO from 'oae-util/lib/io'; -const PreviewConstants = require('oae-preview-processor/lib/constants'); +import { logger } from 'oae-logger'; + +const log = logger('oae-preview-processor'); /** * Downloads a file that is not located on the OAE server and stores it on disk. @@ -161,10 +163,7 @@ const _resizeImages = function(ctx, path, sizes, callback) { gm(path).size((err, sourceSize) => { if (err) { called = true; - log().error( - { err, path, contentId: ctx.content.id }, - 'Could not retrieve the size for this image.' - ); + log().error({ err, path, contentId: ctx.content.id }, 'Could not retrieve the size for this image.'); return callback({ code: 500, msg: err.message }); } @@ -229,13 +228,8 @@ const _resize = function(ctx, path, size, callback) { // If we're dealing with a GIF, we use the first frame inputPath = path + '[0]'; } - log().trace( - { contentId: ctx.contentId }, - 'Resizing image %s to %s x %s', - inputPath, - size.width, - size.height - ); + + log().trace({ contentId: ctx.contentId }, 'Resizing image %s to %s x %s', inputPath, size.width, size.height); ImageUtil.resizeImage(inputPath, size, (err, file) => { if (err) { return callback(err); @@ -304,6 +298,7 @@ const _cropThumbnail = function(ctx, path, cropMode, callback) { if (err) { return callback(err); } + if (!thumbnailPath) { // If we weren't able to generate the thumbnail, that means the source image is too small. There is no point in trying to render the large rectangle return callback(); @@ -332,6 +327,7 @@ const _cropThumbnail = function(ctx, path, cropMode, callback) { if (widePath) { ctx.addPreview(widePath, 'wide'); } + callback(); } ); @@ -429,7 +425,4 @@ const _cropIntelligently = function(ctx, path, width, height, opts, filename, ca }); }; -module.exports = { - downloadRemoteFile, - generatePreviewsFromImage -}; +export { downloadRemoteFile, generatePreviewsFromImage }; diff --git a/packages/oae-preview-processor/tests/test-filters.js b/packages/oae-preview-processor/tests/test-filters.js index 511dd6200b..c08271361d 100644 --- a/packages/oae-preview-processor/tests/test-filters.js +++ b/packages/oae-preview-processor/tests/test-filters.js @@ -13,11 +13,11 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const { Content } = require('oae-content/lib/model'); -const { FilterGenerator } = require('oae-preview-processor/lib/filters'); +import { Content } from 'oae-content/lib/model'; +import { FilterGenerator } from 'oae-preview-processor/lib/filters'; describe('Preview processor - filters', () => { // A set of timestamps (ms since epoch) each a day apart (A = 5 days ago against F, B = 4 days ago, etc) @@ -39,30 +39,8 @@ describe('Preview processor - filters', () => { const _getMockData = function() { // Fake 5 content items const content = [ - new Content( - 'camtest', - 'c:camtest:a', - 'public', - 'A', - 'A', - 'file', - 'u:camtest:simon', - times.A, - times.E, - 'a-3' - ), - new Content( - 'camtest', - 'c:camtest:b', - 'public', - 'B', - 'B', - 'link', - 'u:camtest:nico', - times.B, - times.B, - 'b-1' - ), + new Content('camtest', 'c:camtest:a', 'public', 'A', 'A', 'file', 'u:camtest:simon', times.A, times.E, 'a-3'), + new Content('camtest', 'c:camtest:b', 'public', 'B', 'B', 'link', 'u:camtest:nico', times.B, times.B, 'b-1'), new Content( 'camtest', 'c:camtest:c', @@ -75,30 +53,8 @@ describe('Preview processor - filters', () => { times.D, 'c-3' ), - new Content( - 'camtest', - 'c:camtest:d', - 'public', - 'D', - 'D', - 'file', - 'u:camtest:simon', - times.C, - times.D, - 'd-2' - ), - new Content( - 'gttest', - 'c:gttest:e', - 'public', - 'E', - 'E', - 'file', - 'u:gttest:stuart', - times.D, - times.F, - 'e-2' - ) + new Content('camtest', 'c:camtest:d', 'public', 'D', 'D', 'file', 'u:camtest:simon', times.C, times.D, 'd-2'), + new Content('gttest', 'c:gttest:e', 'public', 'E', 'E', 'file', 'u:gttest:stuart', times.D, times.F, 'e-2') ]; // Give each content item a revision @@ -274,10 +230,7 @@ describe('Preview processor - filters', () => { for (let i = 0; i < filteredContent.length; i++) { assert.strictEqual(filteredContent[i].id, expectations.revisionStage[i].contentId); for (let r = 0; r < expectations.revisionStage[i].revisions.length; r++) { - assert.strictEqual( - filteredContent[i].revisions[r].revisionId, - expectations.revisionStage[i].revisions[r] - ); + assert.strictEqual(filteredContent[i].revisions[r].revisionId, expectations.revisionStage[i].revisions[r]); } } }; @@ -445,18 +398,7 @@ describe('Preview processor - filters', () => { // Ensure that content items without a proper previews object get reprocessed const content = [ - new Content( - 'camtest', - 'c:camtest:a', - 'public', - 'A', - 'A', - 'file', - 'u:camtest:simon', - times.A, - times.E, - 'a-3' - ) + new Content('camtest', 'c:camtest:a', 'public', 'A', 'A', 'file', 'u:camtest:simon', times.A, times.E, 'a-3') ]; const filterGenerator = new FilterGenerator(filters); assert.ok(!filterGenerator.hasErrors()); @@ -490,18 +432,7 @@ describe('Preview processor - filters', () => { // Ensure that revision items without a proper previews object get reprocessed const content = [ - new Content( - 'camtest', - 'c:camtest:a', - 'public', - 'A', - 'A', - 'file', - 'u:camtest:simon', - times.A, - times.E, - 'a-3' - ) + new Content('camtest', 'c:camtest:a', 'public', 'A', 'A', 'file', 'u:camtest:simon', times.A, times.E, 'a-3') ]; content[0].revisions = [ { diff --git a/packages/oae-preview-processor/tests/test-longurl.js b/packages/oae-preview-processor/tests/test-longurl.js index e0ea9db063..9132e31482 100644 --- a/packages/oae-preview-processor/tests/test-longurl.js +++ b/packages/oae-preview-processor/tests/test-longurl.js @@ -13,10 +13,11 @@ * permissions and limitations under the License. */ -const assert = require('assert'); +import assert from 'assert'; +import nock from 'nock'; -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests/lib/util'); +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests/lib/util'; describe('Long url', () => { let anonymousCamRestContext = null; @@ -32,10 +33,6 @@ describe('Long url', () => { * @api private */ const _mockRequests = function() { - // Require nock inline as it messes with the HTTP stack - // We only want this to happen in a controlled environment - const nock = require('nock'); - // Ensure we can still perform regular HTTP requests during our tests nock.enableNetConnect(); @@ -66,18 +63,11 @@ describe('Long url', () => { // Mock the HEAD requests _mockRequests(); - RestAPI.Previews.expandUrl( - anonymousCamRestContext, - 'http://youtu.be/FYWLiGOBy1k', - (err, data) => { - assert.ok(!err); - assert.ok(data); - assert.strictEqual( - data['long-url'], - 'https://www.youtube.com/watch?v=FYWLiGOBy1k&feature=youtu.be' - ); - return callback(); - } - ); + RestAPI.Previews.expandUrl(anonymousCamRestContext, 'http://youtu.be/FYWLiGOBy1k', (err, data) => { + assert.ok(!err); + assert.ok(data); + assert.strictEqual(data['long-url'], 'https://www.youtube.com/watch?v=FYWLiGOBy1k&feature=youtu.be'); + return callback(); + }); }); }); diff --git a/packages/oae-preview-processor/tests/test-previews.js b/packages/oae-preview-processor/tests/test-previews.js index 00962fb1d5..836924b498 100644 --- a/packages/oae-preview-processor/tests/test-previews.js +++ b/packages/oae-preview-processor/tests/test-previews.js @@ -13,44 +13,43 @@ * permissions and limitations under the License. */ -/* eslint-disable no-unused-vars */ /* eslint-disable camelcase */ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const url = require('url'); -const util = require('util'); -const _ = require('underscore'); -const gm = require('gm'); -const less = require('less'); -const request = require('request'); - -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityTestsUtil = require('oae-activity/lib/test/util'); -const Cassandra = require('oae-util/lib/cassandra'); -const ConfigTestUtil = require('oae-config/lib/test/util'); -const ContentTestUtil = require('oae-content/lib/test/util'); -const Etherpad = require('oae-content/lib/internal/etherpad'); -const FoldersPreviews = require('oae-folders/lib/previews'); -const FoldersTestUtil = require('oae-folders/lib/test/util'); -const MQ = require('oae-util/lib/mq'); -const MQTestUtil = require('oae-util/lib/test/mq-util'); -const RestAPI = require('oae-rest'); -const RestUtil = require('oae-rest/lib/util'); -const { SearchConstants } = require('oae-search/lib/constants'); -const TaskQueue = require('oae-util/lib/taskqueue'); -const Tempfile = require('oae-util/lib/tempfile'); -const TestsUtil = require('oae-tests/lib/util'); - -const PreviewAPI = require('oae-preview-processor/lib/api'); -const PreviewConstants = require('oae-preview-processor/lib/constants'); -const PreviewDefaultLinks = require('oae-preview-processor/lib/processors/link/default'); -const PreviewFlickr = require('oae-preview-processor/lib/processors/link/flickr'); -const PreviewOffice = require('oae-preview-processor/lib/processors/file/office'); -const PreviewPDF = require('oae-preview-processor/lib/processors/file/pdf'); -const PreviewSlideShare = require('oae-preview-processor/lib/processors/link/slideshare'); -const PreviewTestUtil = require('oae-preview-processor/lib/test/util'); -const PreviewUtil = require('oae-preview-processor/lib/util'); +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import url from 'url'; +import util from 'util'; +import _ from 'underscore'; +import gm from 'gm'; +import request from 'request'; +import nock from 'nock'; + +import { SearchConstants } from 'oae-search/lib/constants'; +import { ActivityConstants } from 'oae-activity/lib/constants'; + +import * as ActivityTestsUtil from 'oae-activity/lib/test/util'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as ContentTestUtil from 'oae-content/lib/test/util'; +import * as Etherpad from 'oae-content/lib/internal/etherpad'; +import * as FoldersPreviews from 'oae-folders/lib/previews'; +import * as FoldersTestUtil from 'oae-folders/lib/test/util'; +import * as MQ from 'oae-util/lib/mq'; +import * as MQTestUtil from 'oae-util/lib/test/mq-util'; +import * as RestAPI from 'oae-rest'; +import * as RestUtil from 'oae-rest/lib/util'; +import * as TaskQueue from 'oae-util/lib/taskqueue'; +import * as Tempfile from 'oae-util/lib/tempfile'; +import * as TestsUtil from 'oae-tests/lib/util'; +import * as PreviewAPI from 'oae-preview-processor/lib/api'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; +import * as PreviewDefaultLinks from 'oae-preview-processor/lib/processors/link/default'; +import * as PreviewFlickr from 'oae-preview-processor/lib/processors/link/flickr'; +import * as PreviewOffice from 'oae-preview-processor/lib/processors/file/office'; +import * as PreviewPDF from 'oae-preview-processor/lib/processors/file/pdf'; +import * as PreviewSlideShare from 'oae-preview-processor/lib/processors/link/slideshare'; +import * as PreviewTestUtil from 'oae-preview-processor/lib/test/util'; +import * as PreviewUtil from 'oae-preview-processor/lib/util'; describe('Preview processor', () => { // We fill this variable on tests startup with the configuration @@ -871,9 +870,11 @@ describe('Preview processor', () => { if (xFrameOptions) { res.set('x-frame-options', xFrameOptions); } + if (contentDisposition) { res.set('Content-Disposition', contentDisposition); } + return res.send('This is the best page on the webz'); }); @@ -1054,10 +1055,6 @@ describe('Preview processor', () => { * @api private */ const _mockYoutube = function() { - // Require nock inline as it messes with the HTTP stack - // We only want this to happen in a controlled environment - const nock = require('nock'); - // Ensure we can still perform regular HTTP requests during our tests nock.enableNetConnect(); @@ -1235,6 +1232,7 @@ describe('Preview processor', () => { ' //cdn.slidesharecdn.com/ss_thumbnails/apereooae-stateoftheproject-130610122332-phpapp02-thumbnail.jpg?cb=1371114073'; xml += ''; } + return res.status(200).send(xml); }); @@ -2512,6 +2510,7 @@ describe('Preview processor', () => { contentToBeReprocessed.push(data); callback(); }; + TaskQueue.bind(PreviewConstants.MQ.TASK_GENERATE_PREVIEWS, reprocessTracker, null, err => { assert.ok(!err); diff --git a/packages/oae-principals/config/group.js b/packages/oae-principals/config/group.js index 0b4a717b2a..a611615ca0 100644 --- a/packages/oae-principals/config/group.js +++ b/packages/oae-principals/config/group.js @@ -13,42 +13,40 @@ * permissions and limitations under the License. */ -var Fields = require('oae-config/lib/fields'); +import * as Fields from 'oae-config/lib/fields'; -module.exports = { - 'title': 'OAE Principals Module', - 'group': { - 'name': 'Default Group Values', - 'description': 'Default values for new groups', - 'elements': { - 'visibility': new Fields.List('Default Visibility', 'Default visibility for new groups', 'public', [ - { - 'name': 'Public', - 'value': 'public' - }, - { - 'name': 'Logged in users', - 'value': 'loggedin' - }, - { - 'name': 'Private', - 'value': 'private' - } - ]), - 'joinable': new Fields.List('Default joinability', 'Default joinability for new groups', 'no', [ - { - 'name': 'Users can automatically join', - 'value': 'yes' - }, - { - 'name': 'Users can request to join', - 'value': 'request' - }, - { - 'name': 'Users cannot join', - 'value': 'no' - } - ]) - } - } +export const title = 'OAE Principals Module'; +export const group = { + name: 'Default Group Values', + description: 'Default values for new groups', + elements: { + visibility: new Fields.List('Default Visibility', 'Default visibility for new groups', 'public', [ + { + name: 'Public', + value: 'public' + }, + { + name: 'Logged in users', + value: 'loggedin' + }, + { + name: 'Private', + value: 'private' + } + ]), + joinable: new Fields.List('Default joinability', 'Default joinability for new groups', 'no', [ + { + name: 'Users can automatically join', + value: 'yes' + }, + { + name: 'Users can request to join', + value: 'request' + }, + { + name: 'Users cannot join', + value: 'no' + } + ]) + } }; diff --git a/packages/oae-principals/config/recaptcha.js b/packages/oae-principals/config/recaptcha.js index 3c90c48e64..8a787e3e5c 100644 --- a/packages/oae-principals/config/recaptcha.js +++ b/packages/oae-principals/config/recaptcha.js @@ -13,17 +13,17 @@ * permissions and limitations under the License. */ -var Fields = require('oae-config/lib/fields'); +import * as Fields from 'oae-config/lib/fields'; -module.exports = { - 'title': 'OAE Principals Module', - 'recaptcha': { - 'name': 'reCaptcha Configuration', - 'description': 'Define the reCaptcha settings.', - 'elements': { - 'enabled': new Fields.Bool('Enabled', 'Enable reCaptcha for user creation', true), - 'publicKey': new Fields.Text('Public key', 'Public reCaptcha key', '6LcFWdYSAAAAAFRwq3uKrt134ujkWsIpWJX-HdoS'), - 'privateKey': new Fields.Text('Private key', 'Private reCaptcha key', '6LcFWdYSAAAAANrHjt2Y5VJXoICHa95PFDarVcGs', {'suppress': true}) - } - } +export const title = 'OAE Principals Module'; +export const recaptcha = { + name: 'reCaptcha Configuration', + description: 'Define the reCaptcha settings.', + elements: { + enabled: new Fields.Bool('Enabled', 'Enable reCaptcha for user creation', true), + publicKey: new Fields.Text('Public key', 'Public reCaptcha key', '6LcFWdYSAAAAAFRwq3uKrt134ujkWsIpWJX-HdoS'), + privateKey: new Fields.Text('Private key', 'Private reCaptcha key', '6LcFWdYSAAAAANrHjt2Y5VJXoICHa95PFDarVcGs', { + suppress: true + }) + } }; diff --git a/packages/oae-principals/config/termsAndConditions.js b/packages/oae-principals/config/termsAndConditions.js index 46d2782e21..b88f300e95 100644 --- a/packages/oae-principals/config/termsAndConditions.js +++ b/packages/oae-principals/config/termsAndConditions.js @@ -14,25 +14,23 @@ */ /* eslint-disable unicorn/filename-case */ -const Fields = require('oae-config/lib/fields'); +import * as Fields from 'oae-config/lib/fields'; -module.exports = { - title: 'OAE Principals Module', - termsAndConditions: { - name: 'Terms and Conditions', - description: 'Terms and Conditions Configuration', - elements: { - enabled: new Fields.Bool( - 'Terms and Conditions enabled', - 'Whether or not users should agree to the Terms and Conditions', - false - ), - text: new Fields.InternationalizableText( - 'Terms and Conditions text', - 'The full Terms and Conditions text users should agree to before they can use the system', - '', - { suppress: true } - ) - } +export const title = 'OAE Principals Module'; +export const termsAndConditions = { + name: 'Terms and Conditions', + description: 'Terms and Conditions Configuration', + elements: { + enabled: new Fields.Bool( + 'Terms and Conditions enabled', + 'Whether or not users should agree to the Terms and Conditions', + false + ), + text: new Fields.InternationalizableText( + 'Terms and Conditions text', + 'The full Terms and Conditions text users should agree to before they can use the system', + '', + { suppress: true } + ) } }; diff --git a/packages/oae-principals/config/user.js b/packages/oae-principals/config/user.js index 3d9b8c6d6a..8f64947105 100644 --- a/packages/oae-principals/config/user.js +++ b/packages/oae-principals/config/user.js @@ -13,124 +13,127 @@ * permissions and limitations under the License. */ -var Fields = require('oae-config/lib/fields'); +import * as Fields from 'oae-config/lib/fields'; -module.exports = { - 'title': 'OAE Principals Module', - 'user': { - 'name': 'Default User Values', - 'description': 'Default values for new users', - 'elements': { - 'visibility': new Fields.List('Default Visibility', 'Default visibility for new users', 'public', [ - { - 'name': 'Public', - 'value': 'public' - }, - { - 'name': 'Logged in users', - 'value': 'loggedin' - }, - { - 'name': 'Private', - 'value': 'private' - } - ]), - 'emailPreference': new Fields.List('Default Email Preference', 'Default email preference for new users', 'immediate', [ - { - 'name': 'Immediate', - 'value': 'immediate' - }, - { - 'name': 'Daily', - 'value': 'daily' - }, - { - 'name': 'Weekly', - 'value': 'weekly' - }, - { - 'name': 'Never', - 'value': 'never' - } - ]), - 'defaultLanguage': new Fields.List('Default language', 'Default UI language', 'en_GB', [ - { - 'name': 'Afrikaans', - 'value': 'af_ZA' - }, - { - 'name': 'Català', - 'value': 'ca_ES' - }, - { - 'name': 'Cymraeg', - 'value': 'cy_GB' - }, - { - 'name': 'Deutsch', - 'value': 'de_DE' - }, - { - 'name': 'English (UK)', - 'value': 'en_GB' - }, - { - 'name': 'English (US)', - 'value': 'en_US' - }, - { - 'name': 'Español', - 'value': 'es_ES' - }, - { - 'name': 'Français', - 'value': 'fr_FR' - }, - { - 'name': 'हिन्दी', - 'value': 'hi_IN' - }, - { - 'name': 'Italiano', - 'value': 'it_IT' - }, - { - 'name': 'Nederlands', - 'value': 'nl_NL' - }, - { - 'name': 'Polski', - 'value': 'pl_PL' - }, - { - 'name': 'Português', - 'value': 'pt_PT' - }, - { - 'name': 'Português do Brasil', - 'value': 'pt_BR' - }, - { - 'name': 'Русский', - 'value': 'ru_RU' - }, - { - 'name': 'Svenska', - 'value': 'sv_SE' - }, - { - 'name': 'Türkçe', - 'value': 'tr_TR' - }, - { - 'name': 'Valencià', - 'value': 'val_ES' - }, - { - 'name': '中文', - 'value': 'zh_CN' - } - ]) +export const title = 'OAE Principals Module'; +export const user = { + name: 'Default User Values', + description: 'Default values for new users', + elements: { + visibility: new Fields.List('Default Visibility', 'Default visibility for new users', 'public', [ + { + name: 'Public', + value: 'public' + }, + { + name: 'Logged in users', + value: 'loggedin' + }, + { + name: 'Private', + value: 'private' + } + ]), + emailPreference: new Fields.List( + 'Default Email Preference', + 'Default email preference for new users', + 'immediate', + [ + { + name: 'Immediate', + value: 'immediate' + }, + { + name: 'Daily', + value: 'daily' + }, + { + name: 'Weekly', + value: 'weekly' + }, + { + name: 'Never', + value: 'never' } - } + ] + ), + defaultLanguage: new Fields.List('Default language', 'Default UI language', 'en_GB', [ + { + name: 'Afrikaans', + value: 'af_ZA' + }, + { + name: 'Català', + value: 'ca_ES' + }, + { + name: 'Cymraeg', + value: 'cy_GB' + }, + { + name: 'Deutsch', + value: 'de_DE' + }, + { + name: 'English (UK)', + value: 'en_GB' + }, + { + name: 'English (US)', + value: 'en_US' + }, + { + name: 'Español', + value: 'es_ES' + }, + { + name: 'Français', + value: 'fr_FR' + }, + { + name: 'हिन्दी', + value: 'hi_IN' + }, + { + name: 'Italiano', + value: 'it_IT' + }, + { + name: 'Nederlands', + value: 'nl_NL' + }, + { + name: 'Polski', + value: 'pl_PL' + }, + { + name: 'Português', + value: 'pt_PT' + }, + { + name: 'Português do Brasil', + value: 'pt_BR' + }, + { + name: 'Русский', + value: 'ru_RU' + }, + { + name: 'Svenska', + value: 'sv_SE' + }, + { + name: 'Türkçe', + value: 'tr_TR' + }, + { + name: 'Valencià', + value: 'val_ES' + }, + { + name: '中文', + value: 'zh_CN' + } + ]) + } }; diff --git a/packages/oae-principals/lib/api.group.js b/packages/oae-principals/lib/api.group.js index 698bea4982..1145fc9fa6 100644 --- a/packages/oae-principals/lib/api.group.js +++ b/packages/oae-principals/lib/api.group.js @@ -22,7 +22,7 @@ const { AuthzConstants } = require('oae-authz/lib/constants'); const AuthzInvitations = require('oae-authz/lib/invitations'); const AuthzPermissions = require('oae-authz/lib/permissions'); const AuthzUtil = require('oae-authz/lib/util'); -const Config = require('oae-config/lib/api').config('oae-principals'); +const Config = require('oae-config/lib/api').setUpConfig('oae-principals'); const LibraryAPI = require('oae-library'); const log = require('oae-logger').logger('oae-principals'); const MessageBoxAPI = require('oae-messagebox'); @@ -75,6 +75,7 @@ const getFullGroupProfile = function(ctx, groupId, callback) { if (err) { return callback(err); } + if (group.deleted) { return callback({ code: 404, msg: util.format("Couldn't find principal: %s", groupId) }); } @@ -84,6 +85,7 @@ const getFullGroupProfile = function(ctx, groupId, callback) { if (err) { return callback(err); } + if (!permissions.canView) { return callback({ code: 401, msg: 'You do not have access to this group' }); } @@ -115,6 +117,7 @@ const getFullGroupProfile = function(ctx, groupId, callback) { if (err) { return callback(err); } + if (createdBy) { group.createdBy = createdBy; } @@ -139,6 +142,7 @@ const getFullGroupProfile = function(ctx, groupId, callback) { if (err) { return callback(err); } + return callback(null, group); }); } else { @@ -178,6 +182,7 @@ const getMembersLibrary = function(ctx, groupId, start, limit, callback) { if (err) { return callback(err); } + if (group.deleted) { return callback({ code: 404, msg: util.format("Couldn't find principal: %s", groupId) }); } @@ -263,6 +268,7 @@ const _getMembersLibrary = function(ctx, group, hasRole, start, limit, callback) if (err) { return callback(err); } + if (hasRole) { // When there is an explicit role, we always have access and can see the private library hasAccess = true; @@ -297,6 +303,7 @@ const _getMembersLibrary = function(ctx, group, hasRole, start, limit, callback) role: memberEntry.role }; } + return result; }) .compact() @@ -334,6 +341,7 @@ const getMembershipsLibrary = function(ctx, principalId, start, limit, callback) if (err) { return callback(err); } + if (principal.deleted) { return callback({ code: 404, msg: util.format("Couldn't find principal: %s", principalId) }); } @@ -342,6 +350,7 @@ const getMembershipsLibrary = function(ctx, principalId, start, limit, callback) if (err) { return callback(err); } + if (!hasAccess) { return callback({ code: 401, msg: 'You do not have access to this memberships library' }); } @@ -403,6 +412,7 @@ const _getMembershipsLibrary = function(ctx, principalId, visibility, start, lim // Otherwise we can return back to the caller } + // Get the exact amount of items const pagedItems = _items.slice(0, limit); @@ -456,6 +466,7 @@ const getRecentGroupsForUserId = function(ctx, principalId, limit, callback) { if (err) { return callback(err); } + if (principal.deleted) { return callback({ code: 404, msg: util.format("Couldn't find user: %s", principalId) }); } @@ -480,6 +491,7 @@ const _getRecentGroupsForUserId = function(ctx, principalId, limit, callback) { if (err) { return callback(err); } + const sorted = _.sortBy(items, 'latestVisit') .reverse() .slice(0, 5); @@ -545,6 +557,7 @@ const setGroupMembers = function(ctx, groupId, changes, callback) { if (err) { return callback(err); } + if (group.deleted) { return callback({ code: 404, msg: util.format("Couldn't find principal: %s", groupId) }); } @@ -553,6 +566,7 @@ const setGroupMembers = function(ctx, groupId, changes, callback) { if (err) { return callback(err); } + if (_.isEmpty(memberChangeInfo.changes)) { return callback(); } @@ -603,6 +617,7 @@ const leaveGroup = function(ctx, groupId, callback) { if (err) { return callback(err); } + if (group.deleted) { return callback({ code: 404, msg: util.format('Group not found: %s', group.id) }); } @@ -651,6 +666,7 @@ const joinGroup = function(ctx, groupId, callback) { if (err) { return callback(err); } + if (group.deleted) { return callback({ code: 404, msg: util.format("Couldn't find principal: %s", groupId) }); } @@ -862,6 +878,7 @@ const updateGroup = function(ctx, groupId, profileFields, callback) { if (err) { return callback(err); } + if (oldStorageGroup.deleted) { return callback({ code: 404, msg: util.format("Couldn't find principal: %s", groupId) }); } @@ -969,6 +986,7 @@ const restoreGroup = function(ctx, groupId, callback) { if (err) { return callback(err); } + if (!canRestore) { return callback({ code: 401, msg: 'You are not authorized to restore this group' }); } @@ -1005,6 +1023,7 @@ const canRestoreGroup = function(ctx, groupId, callback) { if (err) { return callback(err); } + if (!ctx.user().isAdmin(group.tenant.alias)) { // Only the global or tenant admin can restore a group return callback(null, false); @@ -1078,6 +1097,7 @@ const _canManageAnyGroups = function(ctx, groupIds, callback) { // Try the next group return _canManageAnyGroups(ctx, groupIds, callback); } + if (err) { // A system error occurred return callback(err); @@ -1098,6 +1118,7 @@ const _validateJoinGroupRequest = function(ctx, groupId, callback) { if (validator.hasErrors()) { return callback(validator.getFirstError()); } + return callback(); }; @@ -1223,6 +1244,7 @@ const _validateUpdateJoinGroupByRequest = function(ctx, joinRequest, callback) { .check(role, { code: 400, msg: role + ' is not a recognized role group' }) .isIn(PrincipalsConstants.role.ALL_PRIORITY); } + validator.check(principalId, { code: 400, msg: 'Must specify a valid principalId' }).isPrincipalId(); validator .check(status, { code: 400, msg: status + ' is not a recognized request status' }) @@ -1277,6 +1299,7 @@ const updateJoinGroupByRequest = function(ctx, joinRequest, callback) { } }); } + return notifyOfJoinRequestDecision(ctx, { groupId, principalId, status }, callback); }); }); @@ -1292,6 +1315,7 @@ const notifyOfJoinRequestDecision = function(ctx, joinRequest, callback) { if (err) { return callback(err); } + PrincipalsDAO.getPrincipal(principalId, (err, requester) => { if (err) { return callback(err); diff --git a/packages/oae-principals/lib/api.termsAndConditions.js b/packages/oae-principals/lib/api.termsAndConditions.js index 9dd66c4cbb..bfc1e63ffc 100644 --- a/packages/oae-principals/lib/api.termsAndConditions.js +++ b/packages/oae-principals/lib/api.termsAndConditions.js @@ -18,7 +18,7 @@ const AuthzUtil = require('oae-authz/lib/util'); const ConfigAPI = require('oae-config'); const { Validator } = require('oae-util/lib/validator'); -const PrincipalsConfig = ConfigAPI.config('oae-principals'); +const PrincipalsConfig = ConfigAPI.setUpConfig('oae-principals'); const PrincipalsDAO = require('./internal/dao'); const PrincipalsUtil = require('./util'); @@ -41,19 +41,11 @@ const getTermsAndConditions = function(ctx, locale) { locale = locale || ctx.resolvedLocale(); // Grab the internationalizable field. This will return an object with each Terms and Conditions keyed against its locale - const termsAndConditions = PrincipalsConfig.getValue( - ctx.tenant().alias, - 'termsAndConditions', - 'text' - ); + const termsAndConditions = PrincipalsConfig.getValue(ctx.tenant().alias, 'termsAndConditions', 'text'); return { text: termsAndConditions[locale] || termsAndConditions.default, - lastUpdate: PrincipalsConfig.getLastUpdated( - ctx.tenant().alias, - 'termsAndConditions', - 'text' - ).getTime() + lastUpdate: PrincipalsConfig.getLastUpdated(ctx.tenant().alias, 'termsAndConditions', 'text').getTime() }; }; @@ -129,11 +121,7 @@ const needsToAcceptTermsAndConditions = function(ctx) { } // This tenant has Terms and Conditions. We need to check the user has accepted the Terms and Conditions since the last time the Terms and Conditions were updated - const lastUpdated = PrincipalsConfig.getLastUpdated( - ctx.tenant().alias, - 'termsAndConditions', - 'text' - ); + const lastUpdated = PrincipalsConfig.getLastUpdated(ctx.tenant().alias, 'termsAndConditions', 'text'); return ctx.user().acceptedTC < lastUpdated.getTime(); }; diff --git a/packages/oae-principals/lib/api.user.js b/packages/oae-principals/lib/api.user.js index e98ae963de..a6d07645c3 100644 --- a/packages/oae-principals/lib/api.user.js +++ b/packages/oae-principals/lib/api.user.js @@ -40,7 +40,7 @@ const TenantsAPI = require('oae-tenants'); const TenantsUtil = require('oae-tenants/lib/util'); const Signature = require('oae-util/lib/signature'); -const PrincipalsConfig = ConfigAPI.config('oae-principals'); +const PrincipalsConfig = ConfigAPI.setUpConfig('oae-principals'); const { PrincipalsConstants } = require('./constants'); const PrincipalsDAO = require('./internal/dao'); const PrincipalsEmitter = require('./internal/emitter'); @@ -68,10 +68,7 @@ const HTTPS_PROTOCOL = 'https'; const registerFullUserProfileDecorator = function(namespace, decorator) { if (fullUserProfileDecorators[namespace]) { throw new Error( - util.format( - 'Attempted to register duplicate full user profile decorator with namespace "%s"', - namespace - ) + util.format('Attempted to register duplicate full user profile decorator with namespace "%s"', namespace) ); } else if (!_.isFunction(decorator)) { throw new TypeError( @@ -144,8 +141,7 @@ const createUser = function(ctx, tenantAlias, displayName, opts, callback) { opts.visibility = opts.visibility || PrincipalsConfig.getValue(tenantAlias, 'user', 'visibility'); opts.publicAlias = opts.publicAlias || displayName; opts.acceptedTC = opts.acceptedTC || false; - opts.emailPreference = - opts.emailPreference || PrincipalsConfig.getValue(tenantAlias, 'user', 'emailPreference'); + opts.emailPreference = opts.emailPreference || PrincipalsConfig.getValue(tenantAlias, 'user', 'emailPreference'); const validator = new Validator(); validator.check(displayName, { code: 400, msg: 'A display name must be provided' }).notEmpty(); @@ -160,17 +156,14 @@ const createUser = function(ctx, tenantAlias, displayName, opts, callback) { .isIn(_.values(PrincipalsConstants.emailPreferences)); // If an administrator is creating an account, we consider the email address to be verified - opts.emailVerified = - opts.emailVerified || (ctx.user() && ctx.user().isAdmin(tenantAlias)) || false; + opts.emailVerified = opts.emailVerified || (ctx.user() && ctx.user().isAdmin(tenantAlias)) || false; // Because some SSO strategies do not release an email address, we allow user accounts to be // created without providing the email if (_.isString(opts.email)) { // E-mail addresses are always lower-cased as it makes them easier to deal with opts.email = opts.email.toLowerCase(); - validator - .check(opts.email, { code: 400, msg: 'The specified email address is invalid' }) - .isEmail(); + validator.check(opts.email, { code: 400, msg: 'The specified email address is invalid' }).isEmail(); } else { // Avoid setting a falsey email address delete opts.email; @@ -268,14 +261,7 @@ const _createUser = function(ctx, user, email, callback) { * @param {Function} callback Standard callback function * @param {Object} callback.err An error that occurred, if any */ -const importUsers = function( - ctx, - tenantAlias, - userCSV, - authenticationStrategy, - forceProfileUpdate, - callback -) { +const importUsers = function(ctx, tenantAlias, userCSV, authenticationStrategy, forceProfileUpdate, callback) { tenantAlias = tenantAlias || ctx.user().tenant.alias; forceProfileUpdate = forceProfileUpdate || false; callback = callback || function() {}; @@ -291,9 +277,7 @@ const importUsers = function( // Parameter validation const validator = new Validator(); - validator - .check(tenant, { code: 400, msg: 'An existing tenant alias must be provided' }) - .notNull(); + validator.check(tenant, { code: 400, msg: 'An existing tenant alias must be provided' }).notNull(); validator.check(userCSV, { code: 400, msg: 'A CSV file must be provided' }).notNull(); if (userCSV) { validator.check(userCSV.size, { code: 400, msg: 'Missing size on the CSV file' }).notEmpty(); @@ -301,6 +285,7 @@ const importUsers = function( validator.check(userCSV.size, { code: 400, msg: 'Invalid size on the CSV file' }).min(0); validator.check(userCSV.name, { code: 400, msg: 'Missing name on the CSV file' }).notEmpty(); } + validator .check(authenticationStrategy, { code: 400, @@ -357,11 +342,11 @@ const importUsers = function( ); /*! - * Process an invidual user from the CSV file and create a new user if no user exists for the provided - * external id - authentication strategy combination. - * - * @param {Array.>} data Parsed CSV file - */ + * Process an invidual user from the CSV file and create a new user if no user exists for the provided + * external id - authentication strategy combination. + * + * @param {Array.>} data Parsed CSV file + */ const processUser = function(data) { // Get the next user from the stack const user = data.pop(); @@ -385,10 +370,10 @@ const importUsers = function( }; /*! - * Gets called when the user has been created or updated - * - * @param {Object} err An error object that can be returned by the updateUser call - */ + * Gets called when the user has been created or updated + * + * @param {Object} err An error object that can be returned by the updateUser call + */ const finishImportUser = function(err) { if (err) { log().error({ err, externalId }, 'Failed to import user'); @@ -407,6 +392,7 @@ const importUsers = function( return PrincipalsEmitter.emit('postCSVUserImport'); // Add a progress log statement every 25 imported users } + if (data.length % 25 === 0) { log(ctx).info( { @@ -416,6 +402,7 @@ const importUsers = function( 'Importing users from CSV. ' + data.length + ' users left to import' ); } + // Process the next user processUser(data); }; @@ -437,6 +424,7 @@ const importUsers = function( // If the user already existed it's possible that we need to update it } + if (created) { finishImportUser(); // If the user was created, we can move on to the next one @@ -448,9 +436,11 @@ const importUsers = function( if (user.displayName !== displayName) { update.displayName = displayName; } + if (user.publicAlias !== displayName) { update.publicAlias = displayName; } + if (user.email !== opts.email) { update.email = opts.email; } @@ -459,6 +449,7 @@ const importUsers = function( if (user.displayName === externalId) { update.displayName = displayName; } + if (!user.publicAlias || user.publicAlias === externalId) { update.publicAlias = displayName; } @@ -512,6 +503,7 @@ const _cleanUpCSVFile = function(userCSV, callback) { if (err) { log().warn({ err, file: userCSV }, 'Could not remove the user import CSV file'); } + callback(); }); } else { @@ -553,24 +545,13 @@ const updateUser = function(ctx, userId, profileFields, callback) { .min(1); // Verify that restricted properties aren't set here - const validKeys = [ - 'displayName', - 'visibility', - 'email', - 'emailPreference', - 'publicAlias', - 'locale' - ]; + const validKeys = ['displayName', 'visibility', 'email', 'emailPreference', 'publicAlias', 'locale']; const invalidKeys = _.difference(profileFieldKeys, validKeys); - validator - .check(invalidKeys.length, { code: 400, msg: 'Restricted property was attempted to be set.' }) - .max(0); + validator.check(invalidKeys.length, { code: 400, msg: 'Restricted property was attempted to be set.' }).max(0); // Apply special restrictions on some profile fields if (!_.isUndefined(profileFields.displayName)) { - validator - .check(profileFields.displayName, { code: 400, msg: 'A display name cannot be empty' }) - .notEmpty(); + validator.check(profileFields.displayName, { code: 400, msg: 'A display name cannot be empty' }).notEmpty(); validator .check(profileFields.displayName, { code: 400, @@ -578,6 +559,7 @@ const updateUser = function(ctx, userId, profileFields, callback) { }) .isShortString(); } + if (!_.isUndefined(profileFields.visibility)) { validator .check(profileFields.visibility, { @@ -586,6 +568,7 @@ const updateUser = function(ctx, userId, profileFields, callback) { }) .isIn(_.values(AuthzConstants.visibility)); } + if (!_.isUndefined(profileFields.emailPreference)) { validator .check(profileFields.emailPreference, { @@ -598,17 +581,13 @@ const updateUser = function(ctx, userId, profileFields, callback) { if (_.isString(profileFields.email)) { // E-mail addresses are always lower-cased as it makes them easier to deal with profileFields.email = profileFields.email.toLowerCase(); - validator - .check(profileFields.email, { code: 400, msg: 'The specified email address is invalid' }) - .isEmail(); + validator.check(profileFields.email, { code: 400, msg: 'The specified email address is invalid' }).isEmail(); } else { // Ensure we never set a false-y email delete profileFields.email; } - validator - .check(null, { code: 401, msg: 'You have to be logged in to be able to update a user' }) - .isLoggedInUser(ctx); + validator.check(null, { code: 401, msg: 'You have to be logged in to be able to update a user' }).isLoggedInUser(ctx); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -624,6 +603,7 @@ const updateUser = function(ctx, userId, profileFields, callback) { if (err) { return callback(err); } + if (oldUser.deleted) { return callback({ code: 404, msg: util.format("Couldn't find principal: ", oldUser.id) }); } @@ -635,8 +615,7 @@ const updateUser = function(ctx, userId, profileFields, callback) { // We will make that change once they have verified they own it. We will persist the // desired email address in a separate column family let newEmailAddress = null; - const isEmailChange = - !_.isUndefined(profileFields.email) && profileFields.email !== oldUser.email; + const isEmailChange = !_.isUndefined(profileFields.email) && profileFields.email !== oldUser.email; if (isEmailChange) { newEmailAddress = profileFields.email; delete profileFields.email; @@ -648,21 +627,13 @@ const updateUser = function(ctx, userId, profileFields, callback) { } // If the email address changed but isn't verified, we have to send a verification email - OaeUtil.invokeIfNecessary( - isEmailChange, - _sendEmailToken, - ctx, - newUser, - newEmailAddress, - null, - err => { - if (err) { - return callback(err); - } - - return getUser(ctx, userId, callback); + OaeUtil.invokeIfNecessary(isEmailChange, _sendEmailToken, ctx, newUser, newEmailAddress, null, err => { + if (err) { + return callback(err); } - ); + + return getUser(ctx, userId, callback); + }); }); }); }; @@ -713,6 +684,7 @@ const deleteUser = function(ctx, userId, callback) { if (err) { return callback(err); } + if (!canDelete) { return callback({ code: 401, msg: 'You are not authorized to delete this user' }); } @@ -752,6 +724,7 @@ const deleteOrRestoreUsersByTenancy = function(ctx, tenantAlias, disableUsers, c if (err) { callback(err); } + callback(null, users); }); } else { @@ -759,6 +732,7 @@ const deleteOrRestoreUsersByTenancy = function(ctx, tenantAlias, disableUsers, c if (err) { callback(err); } + callback(null, users); }); } @@ -802,6 +776,7 @@ const getAllUsersForTenant = function(ctx, tenantAlias, callback) { .uniq() .value(); } + return callback(null, users); }); }; @@ -874,6 +849,7 @@ const restoreUser = function(ctx, userId, callback) { if (err) { return callback(err); } + if (!canRestore) { return callback({ code: 401, msg: 'You are not authorized to restore this user' }); } @@ -909,6 +885,7 @@ const canDeleteUser = function(ctx, userId, callback) { if (err) { return callback(err); } + if (ctx.user().id !== userId && !ctx.user().isAdmin(user.tenant.alias)) { // Only an admin or the user themself can delete a user return callback(null, false); @@ -937,6 +914,7 @@ const canRestoreUser = function(ctx, userId, callback) { if (err) { return callback(err); } + if (!ctx.user().isAdmin(user.tenant.alias)) { // Only an admin can restore a user return callback(null, false); @@ -987,6 +965,7 @@ const getFullUserProfile = function(ctx, userId, callback) { if (err) { return callback(err); } + if (user.deleted) { return callback({ code: 404, msg: util.format("Couldn't find principal: ", userId) }); } @@ -1007,8 +986,8 @@ const getFullUserProfile = function(ctx, userId, callback) { const decorations = {}; /*! - * Complete one iteration of the decorators loop. Will invoke the method callback when all decorations have completed - */ + * Complete one iteration of the decorators loop. Will invoke the method callback when all decorations have completed + */ const _finishDecorator = function() { numDecorators--; if (numDecorators === 0) { @@ -1034,6 +1013,7 @@ const getFullUserProfile = function(ctx, userId, callback) { log().warn({ err }, 'Skipping decorator because of an error in the decoration method'); return _finishDecorator(); } + if (decoration === undefined) { // If the decoration wasn't specified, do not apply it to the decorations. However null is a valid // value @@ -1159,6 +1139,7 @@ const setGlobalAdmin = function(ctx, userId, isAdmin, callback) { if (ctx.user() && _.isFunction(ctx.user().isGlobalAdmin) && ctx.user().isGlobalAdmin()) { return _setAdmin(ctx, 'admin:global', isAdmin, userId, callback); } + return callback({ code: 401, msg: 'You do not have sufficient rights to make someone an admin' }); }; @@ -1184,6 +1165,7 @@ const _setAdmin = function(ctx, adminType, isAdmin, principalId, callback) { if (err) { return callback(err); } + if (user.deleted) { return callback({ code: 404, msg: util.format("Couldn't find principal: ", principalId) }); } @@ -1226,8 +1208,7 @@ const _sendEmailToken = function(ctx, user, email, token, callback) { const userToEmail = _.extend({}, user, { email }); const tenant = TenantsAPI.getTenant(user.tenant.alias); - const verificationUrl = - TenantsUtil.getBaseUrl(tenant) + '/?verifyEmail=' + encodeURIComponent(token); + const verificationUrl = TenantsUtil.getBaseUrl(tenant) + '/?verifyEmail=' + encodeURIComponent(token); // Send an email to the specified e-mail address const data = { @@ -1305,9 +1286,7 @@ const verifyEmail = function(ctx, userId, token, callback) { const validator = new Validator(); validator.check(userId, { code: 400, msg: 'A valid user id must be provided' }).isUserId(); validator.check(token, { code: 400, msg: 'A token must be provided' }).notEmpty(); - validator - .check(token, { code: 400, msg: 'An invalid token was provided' }) - .regex(/^[a-zA-Z0-9-_]{7,14}$/); + validator.check(token, { code: 400, msg: 'An invalid token was provided' }).regex(/^[a-zA-Z0-9-_]{7,14}$/); validator .check(null, { code: 401, @@ -1337,6 +1316,7 @@ const verifyEmail = function(ctx, userId, token, callback) { if (err) { return callback(err); } + if (persistedToken !== token) { return callback({ code: 401, msg: 'Wrong token' }); } @@ -1508,10 +1488,7 @@ const exportData = function(ctx, userId, exportType, callback) { // Convert an object to a zip file _objectToZipFile(personalData, (err, zipFile) => { if (err) { - log().error( - { err, displayName: principal.displayName }, - 'An error occurred while creating the zip file' - ); + log().error({ err, displayName: principal.displayName }, 'An error occurred while creating the zip file'); return callback(err); } @@ -1536,6 +1513,7 @@ const _extractProfilePicture = function(ctx, principal, callback) { if (_.isEmpty(principal.picture)) { return callback(); } + const path = ContentUtil.getStorageBackend(ctx, principal.picture.largeUri).getRootDirectory(); const pathLargePicture = principal.picture.largeUri.split(':'); @@ -1587,6 +1565,7 @@ const _assemblePersonalData = function(personalDetails, profilePicture, data, ca personalData.discussion = data.discussionData; } } + callback(); } ], @@ -1666,38 +1645,32 @@ const exportContentData = function(ctx, userId, exportType, callback) { } // Get discussions library - DiscussionsAPI.Discussions.getDiscussionsLibrary( - ctx, - userId, - null, - null, - (err, discussions) => { + DiscussionsAPI.Discussions.getDiscussionsLibrary(ctx, userId, null, null, (err, discussions) => { + if (err) { + return callback(err); + } + + if (exportType === PrincipalsConstants.exportType.CONTENT_DATA) { + discussions = _.reject(discussions, discussion => { + return discussion.createdBy !== userId; + }); + } + + // Convert meetings into txt file + _discussionToTxt(ctx, discussions, (err, discussionData) => { if (err) { return callback(err); } - if (exportType === PrincipalsConstants.exportType.CONTENT_DATA) { - discussions = _.reject(discussions, discussion => { - return discussion.createdBy !== userId; - }); - } - - // Convert meetings into txt file - _discussionToTxt(ctx, discussions, (err, discussionData) => { - if (err) { - return callback(err); - } - - return callback(null, { - uploadData, - linkData, - collabdocData, - meetingData, - discussionData - }); + return callback(null, { + uploadData, + linkData, + collabdocData, + meetingData, + discussionData }); - } - ); + }); + }); }); }); }); @@ -1817,6 +1790,7 @@ const _collabdocToTxt = function(ctx, collabdocs, callback) { if (err) { return callback(err); } + return callback(null, collabdocData); } ); @@ -1880,6 +1854,7 @@ const _linkToTxt = function(ctx, links, callback) { if (err) { return callback(err); } + return callback(null, linkData); } ); @@ -1944,6 +1919,7 @@ const _meetingToTxt = function(ctx, meetings, callback) { if (err) { return callback(err); } + return callback(null, meetingData); } ); @@ -2008,6 +1984,7 @@ const _discussionToTxt = function(ctx, discussions, callback) { if (err) { return callback(err); } + return callback(null, discussionData); } ); @@ -2087,6 +2064,7 @@ const _objectToZipFile = function(personalData, callback) { zipFile.file('personal_data.txt', personalData.personalDetails); return callback(); } + return callback(); }; @@ -2096,6 +2074,7 @@ const _objectToZipFile = function(personalData, callback) { if (err) { return callback(err); } + zipFile.file(personalData.profilePicture.imageName, data, { base64: false, binary: true }); return callback(); }); @@ -2115,6 +2094,7 @@ const _objectToZipFile = function(personalData, callback) { if (err) { return callback(err); } + const fileExt = uploadedFile.title.split('.').pop(); const text = uploadedFile.title.split('.'); const fileName = text.slice(0, text.length - 1).join('.'); @@ -2128,6 +2108,7 @@ const _objectToZipFile = function(personalData, callback) { if (err) { return callback(err); } + return callback(); } ); @@ -2151,6 +2132,7 @@ const _objectToZipFile = function(personalData, callback) { return callback(); } + return callback(); }; @@ -2169,6 +2151,7 @@ const _objectToZipFile = function(personalData, callback) { return callback(); } + return callback(); }; @@ -2187,6 +2170,7 @@ const _objectToZipFile = function(personalData, callback) { return callback(); } + return callback(); }; @@ -2205,6 +2189,7 @@ const _objectToZipFile = function(personalData, callback) { return callback(); } + return callback(); }; @@ -2222,6 +2207,7 @@ const _objectToZipFile = function(personalData, callback) { if (err) { return callback(err); } + return callback(null, zipFile); } ); @@ -2254,6 +2240,7 @@ const _getNewFileName = function(fileExt, fileName, folder) { if (index !== 0) { return fileName + '(' + index + ').' + fileExt; } + return fileName + '.' + fileExt; } } diff --git a/packages/oae-principals/lib/internal/dao.js b/packages/oae-principals/lib/internal/dao.js index c92fb3cc68..54dc8b4d45 100644 --- a/packages/oae-principals/lib/internal/dao.js +++ b/packages/oae-principals/lib/internal/dao.js @@ -25,7 +25,7 @@ const { Validator } = require('oae-authz/lib/validator'); const _ = require('underscore'); const { Group } = require('oae-principals/lib/model'); -const PrincipalsConfig = require('oae-config').config('oae-principals'); +const PrincipalsConfig = require('oae-config').setUpConfig('oae-principals'); const { User } = require('oae-principals/lib/model'); const { PrincipalsConstants } = require('../constants'); @@ -178,6 +178,7 @@ const getExistingPrincipals = function(principalIds, fields, callback) { if (err) { return callback(err); } + if (_.keys(principalsById).length !== principalIds.length) { return callback({ code: 400, msg: 'One or more provided principals did not exist' }); } @@ -209,6 +210,7 @@ const getPrincipals = function(principalIds, fields, callback) { // even if it is just a listing of 1 principal (e.g., a library of 1 item) return callback(null, {}); } + if (err) { return callback(err); } @@ -423,6 +425,7 @@ const getEmailToken = function(userId, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback({ code: 404, msg: 'No email token found for the given user id' }); } @@ -723,6 +726,7 @@ const _isEmailAddressUpdate = function(principalId, profileFields, callback) { if (err) { return callback(err); } + if (user.email !== profileFields.email) { return callback(null, true, user.email); } @@ -745,6 +749,7 @@ const _getPrincipalFromCassandra = function(principalId, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback({ code: 404, msg: "Couldn't find principal: " + principalId }); } @@ -817,6 +822,7 @@ const _getUserFromRedis = function(userId, callback) { // Since we also push updates into redis, use the user id as a slug to ensure that the user doesn't exist in // cache by virtue of an upsert } + if (!hash || !hash.principalId) { return callback({ code: 404, msg: 'Principal not found in redis' }); } @@ -974,6 +980,7 @@ const getVisitedGroups = function(userId, callback) { if (isUser(userId)) { return _getVisitedGroupsFromCassandra(userId, callback); } + return callback({ code: 404, msg: "Couldn't find user: " + userId }); }; @@ -991,6 +998,7 @@ const _getVisitedGroupsFromCassandra = function(userId, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback(null, []); } @@ -1020,6 +1028,7 @@ const getAllUsersForTenant = function(tenantAlias, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback(null, []); } @@ -1102,6 +1111,7 @@ const getJoinGroupRequest = function(groupId, principalId, callback) { if (err) { return callback(err); } + if (_.isEmpty(row)) { return callback(); } @@ -1124,6 +1134,7 @@ const getJoinGroupRequests = function(groupId, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { return callback(); } diff --git a/packages/oae-principals/lib/rest.user.js b/packages/oae-principals/lib/rest.user.js index f5e092ab9b..c2caff45a8 100644 --- a/packages/oae-principals/lib/rest.user.js +++ b/packages/oae-principals/lib/rest.user.js @@ -22,7 +22,7 @@ const { LoginId } = require('oae-authentication/lib/model'); const OAE = require('oae-util/lib/oae'); const OaeUtil = require('oae-util/lib/util'); -const PrincipalsConfig = require('oae-config').config('oae-principals'); +const PrincipalsConfig = require('oae-config').setUpConfig('oae-principals'); const PrincipalsAPI = require('./api'); /** diff --git a/packages/oae-principals/tests/test-activity.js b/packages/oae-principals/tests/test-activity.js index c1c2af99a8..dcbac635ab 100644 --- a/packages/oae-principals/tests/test-activity.js +++ b/packages/oae-principals/tests/test-activity.js @@ -13,21 +13,18 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const url = require('url'); -const ShortId = require('shortid'); -const _ = require('underscore'); - -const ActivityTestsUtil = require('oae-activity/lib/test/util'); -const AuthzAPI = require('oae-authz'); -const AuthzUtil = require('oae-authz/lib/util'); -const EmailTestsUtil = require('oae-email/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const RestUtil = require('oae-rest/lib/util'); -const TestsUtil = require('oae-tests'); +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import url from 'url'; +import _ from 'underscore'; + +import * as ActivityTestsUtil from 'oae-activity/lib/test/util'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as EmailTestsUtil from 'oae-email/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as RestUtil from 'oae-rest/lib/util'; +import * as TestsUtil from 'oae-tests'; const PrincipalsTestUtil = require('oae-principals/lib/test/util'); @@ -82,6 +79,7 @@ describe('Principals Activity', () => { return activity; } } + return null; }; diff --git a/packages/oae-principals/tests/test-cropping.js b/packages/oae-principals/tests/test-cropping.js index 815fa8c51c..738a2ddfed 100644 --- a/packages/oae-principals/tests/test-cropping.js +++ b/packages/oae-principals/tests/test-cropping.js @@ -13,23 +13,23 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const querystring = require('querystring'); -const url = require('url'); -const gm = require('gm'); -const _ = require('underscore'); - -const LocalStorage = require('oae-content/lib/backends/local'); -const RestAPI = require('oae-rest'); -const RestUtil = require('oae-rest/lib/util'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); - -const { PrincipalsConstants } = require('oae-principals/lib/constants'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); -const PrincipalsUtil = require('oae-principals/lib/util'); +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import querystring from 'querystring'; +import url from 'url'; +import gm from 'gm'; +import _ from 'underscore'; + +import * as LocalStorage from 'oae-content/lib/backends/local'; +import * as RestAPI from 'oae-rest'; +import * as RestUtil from 'oae-rest/lib/util'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; + +import { PrincipalsConstants } from 'oae-principals/lib/constants'; describe('Profile pictures', () => { // Rest context that can be used every time we need to make a request as a global admin @@ -474,44 +474,24 @@ describe('Profile pictures', () => { RestAPI.Group.downloadPicture(ctx, groupA.group.id, 'medium', (err, body, response) => { assert.ok(!err); assert.strictEqual(response.statusCode, 204); - RestAPI.Group.downloadPicture( - ctx, - groupA.group.id, - 'large', - (err, body, response) => { - assert.ok(!err); - assert.strictEqual(response.statusCode, 204); - - // Now try downloading it with some invalid parameters - RestAPI.Group.downloadPicture( - ctx, - 'invalid-group-id', - 'small', - (err, body, response) => { - assert.strictEqual(err.code, 400); - RestAPI.Group.downloadPicture( - ctx, - groupA.group.id, - null, - (err, body, response) => { - assert.strictEqual(err.code, 400); - - // The other group has no picture, this should result in a 404 - RestAPI.Group.downloadPicture( - ctx, - groupB.group.id, - 'small', - (err, body, response) => { - assert.strictEqual(err.code, 404); - callback(); - } - ); - } - ); - } - ); - } - ); + RestAPI.Group.downloadPicture(ctx, groupA.group.id, 'large', (err, body, response) => { + assert.ok(!err); + assert.strictEqual(response.statusCode, 204); + + // Now try downloading it with some invalid parameters + RestAPI.Group.downloadPicture(ctx, 'invalid-group-id', 'small', (err, body, response) => { + assert.strictEqual(err.code, 400); + RestAPI.Group.downloadPicture(ctx, groupA.group.id, null, (err, body, response) => { + assert.strictEqual(err.code, 400); + + // The other group has no picture, this should result in a 404 + RestAPI.Group.downloadPicture(ctx, groupB.group.id, 'small', (err, body, response) => { + assert.strictEqual(err.code, 404); + callback(); + }); + }); + }); + }); }); }); }); @@ -539,16 +519,11 @@ describe('Profile pictures', () => { RestAPI.Group.downloadPicture(ctx, group.id, 'medium', (err, body, request) => { assert.ok(!err); assert.strictEqual(request.statusCode, 204); - RestAPI.Group.downloadPicture( - ctx, - group.id, - 'large', - (err, body, request) => { - assert.ok(!err); - assert.strictEqual(request.statusCode, 204); - callback(); - } - ); + RestAPI.Group.downloadPicture(ctx, group.id, 'large', (err, body, request) => { + assert.ok(!err); + assert.strictEqual(request.statusCode, 204); + callback(); + }); }); }); }); @@ -581,10 +556,16 @@ describe('Profile pictures', () => { err => { assert.ok(!err); - RestAPI.User.getUser( - contexts.nicolaas.restContext, - contexts.simon.user.id, - (err, user) => { + RestAPI.User.getUser(contexts.nicolaas.restContext, contexts.simon.user.id, (err, user) => { + assert.ok(!err); + assert.strictEqual(user.picture.small, undefined); + assert.strictEqual(user.picture.smallUri, undefined); + assert.strictEqual(user.picture.medium, undefined); + assert.strictEqual(user.picture.mediumUri, undefined); + assert.strictEqual(user.picture.large, undefined); + assert.strictEqual(user.picture.largeUri, undefined); + + RestAPI.User.getUser(anonymousRestContext, contexts.simon.user.id, (err, user) => { assert.ok(!err); assert.strictEqual(user.picture.small, undefined); assert.strictEqual(user.picture.smallUri, undefined); @@ -593,60 +574,38 @@ describe('Profile pictures', () => { assert.strictEqual(user.picture.large, undefined); assert.strictEqual(user.picture.largeUri, undefined); - RestAPI.User.getUser( - anonymousRestContext, + RestAPI.User.updateUser( + contexts.simon.restContext, contexts.simon.user.id, - (err, user) => { + { visibility: 'loggedin' }, + err => { assert.ok(!err); - assert.strictEqual(user.picture.small, undefined); - assert.strictEqual(user.picture.smallUri, undefined); - assert.strictEqual(user.picture.medium, undefined); - assert.strictEqual(user.picture.mediumUri, undefined); - assert.strictEqual(user.picture.large, undefined); - assert.strictEqual(user.picture.largeUri, undefined); - - RestAPI.User.updateUser( - contexts.simon.restContext, - contexts.simon.user.id, - { visibility: 'loggedin' }, - err => { - assert.ok(!err); - RestAPI.User.getUser( - contexts.nicolaas.restContext, - contexts.simon.user.id, - (err, user) => { - assert.ok(!err); - assert.ok(user.picture.small); - assert.ok(!user.picture.smallUri); - assert.ok(user.picture.medium); - assert.ok(!user.picture.mediumUri); - assert.ok(user.picture.large); - assert.ok(!user.picture.largeUri); - - // The user who owns the pictures can see everything - RestAPI.User.getUser( - contexts.simon.restContext, - contexts.simon.user.id, - (err, user) => { - assert.ok(!err); - assert.ok(user.picture.small); - assert.ok(!user.picture.smallUri); - assert.ok(user.picture.medium); - assert.ok(!user.picture.mediumUri); - assert.ok(user.picture.large); - assert.ok(!user.picture.largeUri); - callback(); - } - ); - } - ); - } - ); + RestAPI.User.getUser(contexts.nicolaas.restContext, contexts.simon.user.id, (err, user) => { + assert.ok(!err); + assert.ok(user.picture.small); + assert.ok(!user.picture.smallUri); + assert.ok(user.picture.medium); + assert.ok(!user.picture.mediumUri); + assert.ok(user.picture.large); + assert.ok(!user.picture.largeUri); + + // The user who owns the pictures can see everything + RestAPI.User.getUser(contexts.simon.restContext, contexts.simon.user.id, (err, user) => { + assert.ok(!err); + assert.ok(user.picture.small); + assert.ok(!user.picture.smallUri); + assert.ok(user.picture.medium); + assert.ok(!user.picture.mediumUri); + assert.ok(user.picture.large); + assert.ok(!user.picture.largeUri); + callback(); + }); + }); } ); - } - ); + }); + }); } ); } @@ -685,27 +644,20 @@ describe('Profile pictures', () => { _createUsers(contexts => { TestsUtil.generateTestGroups(contexts.simon.restContext, 1, group => { group = group.group; - RestAPI.Group.uploadPicture( - contexts.nicolaas.restContext, - group.id, - _getPictureStream, - null, - err => { - assert.strictEqual(err.code, 401); - _verifyCropping( - contexts.nicolaas.restContext, - group, - _createSelectedArea(10, 10, 200), - 401, - () => { - // Making Nico a member should still not allow him to change the picture. - const members = {}; - members[contexts.nicolaas.user.id] = 'member'; - RestAPI.Group.setGroupMembers( - contexts.simon.restContext, - group.id, - members, - err => { + RestAPI.Group.uploadPicture(contexts.nicolaas.restContext, group.id, _getPictureStream, null, err => { + assert.strictEqual(err.code, 401); + _verifyCropping(contexts.nicolaas.restContext, group, _createSelectedArea(10, 10, 200), 401, () => { + // Making Nico a member should still not allow him to change the picture. + const members = {}; + members[contexts.nicolaas.user.id] = 'member'; + RestAPI.Group.setGroupMembers(contexts.simon.restContext, group.id, members, err => { + assert.ok(!err); + RestAPI.Group.uploadPicture(contexts.nicolaas.restContext, group.id, _getPictureStream, null, err => { + assert.strictEqual(err.code, 401); + _verifyCropping(contexts.nicolaas.restContext, group, _createSelectedArea(10, 10, 200), 401, () => { + // Making him a manager should. + members[contexts.nicolaas.user.id] = 'manager'; + RestAPI.Group.setGroupMembers(contexts.simon.restContext, group.id, members, err => { assert.ok(!err); RestAPI.Group.uploadPicture( contexts.nicolaas.restContext, @@ -713,49 +665,22 @@ describe('Profile pictures', () => { _getPictureStream, null, err => { - assert.strictEqual(err.code, 401); + assert.ok(!err); _verifyCropping( contexts.nicolaas.restContext, group, _createSelectedArea(10, 10, 200), - 401, - () => { - // Making him a manager should. - members[contexts.nicolaas.user.id] = 'manager'; - RestAPI.Group.setGroupMembers( - contexts.simon.restContext, - group.id, - members, - err => { - assert.ok(!err); - RestAPI.Group.uploadPicture( - contexts.nicolaas.restContext, - group.id, - _getPictureStream, - null, - err => { - assert.ok(!err); - _verifyCropping( - contexts.nicolaas.restContext, - group, - _createSelectedArea(10, 10, 200), - 200, - callback - ); - } - ); - } - ); - } + 200, + callback ); } ); - } - ); - } - ); - } - ); + }); + }); + }); + }); + }); + }); }); }); }); @@ -807,13 +732,9 @@ describe('Profile pictures', () => { // Get the URIs and check that they are not removed on the filesystem const smallPicturePath = - rootFilesDir + - '/' + - _getUriFromDownloadUrl(firstRequestUser.picture.small).split(':')[1]; + rootFilesDir + '/' + _getUriFromDownloadUrl(firstRequestUser.picture.small).split(':')[1]; const mediumPicturePath = - rootFilesDir + - '/' + - _getUriFromDownloadUrl(firstRequestUser.picture.medium).split(':')[1]; + rootFilesDir + '/' + _getUriFromDownloadUrl(firstRequestUser.picture.medium).split(':')[1]; assert.strictEqual( fs.existsSync(smallPicturePath), true, @@ -983,18 +904,9 @@ describe('Profile pictures', () => { users[results.results[i].id] = results.results[i]; } - assert.strictEqual( - Object.prototype.hasOwnProperty.call(users[publicUserId], 'thumbnailUrl'), - canPublic - ); - assert.strictEqual( - Object.prototype.hasOwnProperty.call(users[loggedinUserId], 'thumbnailUrl'), - canLoggedIn - ); - assert.strictEqual( - Object.prototype.hasOwnProperty.call(users[privateUserId], 'thumbnailUrl'), - canPrivate - ); + assert.strictEqual(Object.prototype.hasOwnProperty.call(users[publicUserId], 'thumbnailUrl'), canPublic); + assert.strictEqual(Object.prototype.hasOwnProperty.call(users[loggedinUserId], 'thumbnailUrl'), canLoggedIn); + assert.strictEqual(Object.prototype.hasOwnProperty.call(users[privateUserId], 'thumbnailUrl'), canPrivate); callback(); }); }; @@ -1012,64 +924,67 @@ describe('Profile pictures', () => { const privateUser = _.values(users)[2]; const nonMemberUser = _.values(users)[3]; - RestAPI.User.updateUser( - loggedInUser.restContext, - loggedInUser.user.id, - { visibility: 'loggedin' }, - err => { + RestAPI.User.updateUser(loggedInUser.restContext, loggedInUser.user.id, { visibility: 'loggedin' }, err => { + assert.ok(!err); + + RestAPI.User.updateUser(privateUser.restContext, privateUser.user.id, { visibility: 'private' }, err => { assert.ok(!err); - RestAPI.User.updateUser( - privateUser.restContext, - privateUser.user.id, - { visibility: 'private' }, + // Each user has a profile picture + const selectedArea = _createSelectedArea(10, 10, 200, 200); + RestAPI.User.uploadPicture( + publicUser.restContext, + publicUser.user.id, + _getPictureStream, + selectedArea, err => { assert.ok(!err); - // Each user has a profile picture - const selectedArea = _createSelectedArea(10, 10, 200, 200); RestAPI.User.uploadPicture( - publicUser.restContext, - publicUser.user.id, + loggedInUser.restContext, + loggedInUser.user.id, _getPictureStream, selectedArea, err => { assert.ok(!err); RestAPI.User.uploadPicture( - loggedInUser.restContext, - loggedInUser.user.id, + privateUser.restContext, + privateUser.user.id, _getPictureStream, selectedArea, err => { assert.ok(!err); - RestAPI.User.uploadPicture( + // Create a group with the two other members + const groupName = TestsUtil.generateTestUserId('someGroupName'); + RestAPI.Group.createGroup( privateUser.restContext, - privateUser.user.id, - _getPictureStream, - selectedArea, - err => { + groupName, + groupName, + 'public', + 'no', + [], + [loggedInUser.user.id, publicUser.user.id], + (err, group) => { assert.ok(!err); - // Create a group with the two other members - const groupName = TestsUtil.generateTestUserId('someGroupName'); - RestAPI.Group.createGroup( - privateUser.restContext, - groupName, - groupName, - 'public', - 'no', - [], - [loggedInUser.user.id, publicUser.user.id], - (err, group) => { - assert.ok(!err); - - // Perform one search where we wait for the search index to refresh so all subsequent search requests don't have to wait - SearchTestsUtil.whenIndexingComplete(() => { - // The public member can only see his own thumbnail and the loggedin user + // Perform one search where we wait for the search index to refresh so all subsequent search requests don't have to wait + SearchTestsUtil.whenIndexingComplete(() => { + // The public member can only see his own thumbnail and the loggedin user + _verifySearchThumbnails( + publicUser.restContext, + group.id, + true, + true, + false, + publicUser.user.id, + loggedInUser.user.id, + privateUser.user.id, + () => { + // The 'logged in' user can see his own thumbnail and the public one _verifySearchThumbnails( - publicUser.restContext, + loggedInUser.restContext, group.id, true, true, @@ -1078,36 +993,23 @@ describe('Profile pictures', () => { loggedInUser.user.id, privateUser.user.id, () => { - // The 'logged in' user can see his own thumbnail and the public one - _verifySearchThumbnails( - loggedInUser.restContext, + // The private user can see everyone's thumbnail + return _verifySearchThumbnails( + privateUser.restContext, group.id, true, true, - false, + true, publicUser.user.id, loggedInUser.user.id, privateUser.user.id, - () => { - // The private user can see everyone's thumbnail - return _verifySearchThumbnails( - privateUser.restContext, - group.id, - true, - true, - true, - publicUser.user.id, - loggedInUser.user.id, - privateUser.user.id, - callback - ); - } + callback ); } ); - }); - } - ); + } + ); + }); } ); } @@ -1116,8 +1018,8 @@ describe('Profile pictures', () => { ); } ); - } - ); + }); + }); }); }); @@ -1135,108 +1037,75 @@ describe('Profile pictures', () => { TestsUtil.generateTestGroups(simon.restContext, 4, (oaeTeam, backendTeam, uiTeam, qaTeam) => { // Upload pictures for the sub teams const selectedArea = _createSelectedArea(10, 10, 200, 200); - RestAPI.User.uploadPicture( - simon.restContext, - backendTeam.group.id, - _getPictureStream, - selectedArea, - err => { + RestAPI.User.uploadPicture(simon.restContext, backendTeam.group.id, _getPictureStream, selectedArea, err => { + assert.ok(!err); + + RestAPI.User.uploadPicture(simon.restContext, uiTeam.group.id, _getPictureStream, selectedArea, err => { assert.ok(!err); - RestAPI.User.uploadPicture( - simon.restContext, - uiTeam.group.id, - _getPictureStream, - selectedArea, - err => { + RestAPI.User.uploadPicture(simon.restContext, qaTeam.group.id, _getPictureStream, selectedArea, err => { + assert.ok(!err); + + // Make the uiTeam loggedin. + RestAPI.Group.updateGroup(simon.restContext, uiTeam.group.id, { visibility: 'loggedin' }, err => { assert.ok(!err); - RestAPI.User.uploadPicture( - simon.restContext, - qaTeam.group.id, - _getPictureStream, - selectedArea, - err => { + // Make the qa team private. + RestAPI.Group.updateGroup(simon.restContext, qaTeam.group.id, { visibility: 'private' }, err => { + assert.ok(!err); + + // Make the backend, ui and qa teams member of oae team. + const changes = {}; + changes[backendTeam.group.id] = 'member'; + changes[uiTeam.group.id] = 'member'; + changes[qaTeam.group.id] = 'member'; + RestAPI.Group.setGroupMembers(simon.restContext, oaeTeam.group.id, changes, err => { assert.ok(!err); - // Make the uiTeam loggedin. - RestAPI.Group.updateGroup( + // Search through the memberlist of oaeTeam and filter the results so we only get the backend team group back. + SearchTestsUtil.searchAll( simon.restContext, - uiTeam.group.id, - { visibility: 'loggedin' }, - err => { + 'members-library', + [oaeTeam.group.id], + { q: '' }, + (err, results) => { assert.ok(!err); + assert.strictEqual(results.total, 4); + + // We only need the groups + results.results = _.filter(results.results, result => { + return result.resourceType !== 'user'; + }); + + // All the groups should expose their thumbnail regardless of their visibility setting. + assert.ok(results.results[0].thumbnailUrl); + assert.ok(results.results[1].thumbnailUrl); + assert.ok(results.results[2].thumbnailUrl); - // Make the qa team private. - RestAPI.Group.updateGroup( + // Try downloading it by just using the returned url. + RestUtil.performRestRequest( simon.restContext, - qaTeam.group.id, - { visibility: 'private' }, - err => { + results.results[0].thumbnailUrl, + 'GET', + null, + (err, body, response) => { assert.ok(!err); - - // Make the backend, ui and qa teams member of oae team. - const changes = {}; - changes[backendTeam.group.id] = 'member'; - changes[uiTeam.group.id] = 'member'; - changes[qaTeam.group.id] = 'member'; - RestAPI.Group.setGroupMembers( - simon.restContext, - oaeTeam.group.id, - changes, - err => { - assert.ok(!err); - - // Search through the memberlist of oaeTeam and filter the results so we only get the backend team group back. - SearchTestsUtil.searchAll( - simon.restContext, - 'members-library', - [oaeTeam.group.id], - { q: '' }, - (err, results) => { - assert.ok(!err); - assert.strictEqual(results.total, 4); - - // We only need the groups - results.results = _.filter(results.results, result => { - return result.resourceType !== 'user'; - }); - - // All the groups should expose their thumbnail regardless of their visibility setting. - assert.ok(results.results[0].thumbnailUrl); - assert.ok(results.results[1].thumbnailUrl); - assert.ok(results.results[2].thumbnailUrl); - - // Try downloading it by just using the returned url. - RestUtil.performRestRequest( - simon.restContext, - results.results[0].thumbnailUrl, - 'GET', - null, - (err, body, response) => { - assert.ok(!err); - // Downloading happens via nginx, so we can't verify the response body. - // We can verify if the status code is a 204 and if the appropriate headers are present. - assert.strictEqual(response.statusCode, 204); - assert.ok(response.headers['x-accel-redirect']); - assert.ok(response.headers['content-disposition']); - callback(); - } - ); - } - ); - } - ); + // Downloading happens via nginx, so we can't verify the response body. + // We can verify if the status code is a 204 and if the appropriate headers are present. + assert.strictEqual(response.statusCode, 204); + assert.ok(response.headers['x-accel-redirect']); + assert.ok(response.headers['content-disposition']); + callback(); } ); } ); - } - ); - } - ); - } - ); + }); + }); + }); + }); + }); + }); }); }); }); diff --git a/packages/oae-principals/tests/test-dao.js b/packages/oae-principals/tests/test-dao.js index 663598ebef..c3afe364ba 100644 --- a/packages/oae-principals/tests/test-dao.js +++ b/packages/oae-principals/tests/test-dao.js @@ -13,13 +13,12 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import * as TestsUtil from 'oae-tests'; describe('Principals DAO', () => { // Rest contexts that will be used for requests @@ -52,40 +51,35 @@ describe('Principals DAO', () => { simong.user.id, { email }, (simong1, emailToken) => { - PrincipalsTestUtil.assertVerifyEmailSucceeds( - simong.restContext, - simong.user.id, - emailToken, - () => { - // Ensure both users are represented in the user email mapping for the email - PrincipalsDAO.getUserIdsByEmails([email], (err, userIdsByEmail) => { + PrincipalsTestUtil.assertVerifyEmailSucceeds(simong.restContext, simong.user.id, emailToken, () => { + // Ensure both users are represented in the user email mapping for the email + PrincipalsDAO.getUserIdsByEmails([email], (err, userIdsByEmail) => { + assert.ok(!err); + + const ids = userIdsByEmail[email]; + assert.ok(_.isArray(ids)); + assert.strictEqual(_.size(ids), 2); + assert.ok(_.contains(ids, mrvisser.user.id)); + assert.ok(_.contains(ids, simong.user.id)); + + // Now change simong's email to something else using the DAO + const email1 = TestsUtil.generateTestEmailAddress().toLowerCase(); + PrincipalsDAO.updatePrincipal(simong.user.id, { email: email1 }, err => { assert.ok(!err); - const ids = userIdsByEmail[email]; - assert.ok(_.isArray(ids)); - assert.strictEqual(_.size(ids), 2); - assert.ok(_.contains(ids, mrvisser.user.id)); - assert.ok(_.contains(ids, simong.user.id)); - - // Now change simong's email to something else using the DAO - const email1 = TestsUtil.generateTestEmailAddress().toLowerCase(); - PrincipalsDAO.updatePrincipal(simong.user.id, { email: email1 }, err => { + // Ensure mrvisser's email entry still has him mapped + PrincipalsDAO.getUserIdsByEmails([email], (err, userIdsByEmail) => { assert.ok(!err); - // Ensure mrvisser's email entry still has him mapped - PrincipalsDAO.getUserIdsByEmails([email], (err, userIdsByEmail) => { - assert.ok(!err); - - const ids = userIdsByEmail[email]; - assert.ok(_.isArray(ids)); - assert.strictEqual(_.size(ids), 1); - assert.strictEqual(ids[0], mrvisser.user.id); - return callback(); - }); + const ids = userIdsByEmail[email]; + assert.ok(_.isArray(ids)); + assert.strictEqual(_.size(ids), 1); + assert.strictEqual(ids[0], mrvisser.user.id); + return callback(); }); }); - } - ); + }); + }); } ); }); @@ -118,17 +112,13 @@ describe('Principals DAO', () => { assert.strictEqual(err.code, 400); assert.strictEqual(err.msg, 'Attempted to update an invalid property'); - PrincipalsDAO.updatePrincipal( - mrvisser.id, - { 'admin:tenant': true, 'admin:global': true }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.strictEqual(err.msg, 'Attempted to update an invalid property'); + PrincipalsDAO.updatePrincipal(mrvisser.id, { 'admin:tenant': true, 'admin:global': true }, err => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.strictEqual(err.msg, 'Attempted to update an invalid property'); - return callback(); - } - ); + return callback(); + }); }); }); }); @@ -174,8 +164,8 @@ describe('Principals DAO', () => { let foundUser = false; /*! - * Verifies that each principal row only has the principalId - */ + * Verifies that each principal row only has the principalId + */ const _onEach = function(principalRows, done) { // Ensure we only get the principalId of the users _.each(principalRows, principalRow => { @@ -203,8 +193,8 @@ describe('Principals DAO', () => { foundUser = false; /*! - * Verifies that we only get the principalId and displayName of each principalRow - */ + * Verifies that we only get the principalId and displayName of each principalRow + */ const _onEach = function(principalRows, done) { // Ensure we only get the principalId and displayName of the principal _.each(principalRows, principalRow => { diff --git a/packages/oae-principals/tests/test-delete.js b/packages/oae-principals/tests/test-delete.js index d51eed02ca..fe6a6fa071 100644 --- a/packages/oae-principals/tests/test-delete.js +++ b/packages/oae-principals/tests/test-delete.js @@ -13,20 +13,17 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); - -const AuthzAPI = require('oae-authz'); -const AuthzUtil = require('oae-authz/lib/util'); -const FollowingTestUtil = require('oae-following/lib/test/util'); -const Redis = require('oae-util/lib/redis'); -const RestAPI = require('oae-rest'); -const SearchTestUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); - -const PrincipalsDelete = require('oae-principals/lib/delete'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); -const DisableUsersMigration = require('../../../etc/migration/disable_users_from_tenancy/lib/disable-users-by-tenancy'); +import assert from 'assert'; +import _ from 'underscore'; + +import * as FollowingTestUtil from 'oae-following/lib/test/util'; +import * as Redis from 'oae-util/lib/redis'; +import * as RestAPI from 'oae-rest'; +import * as SearchTestUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests'; +import * as PrincipalsDelete from 'oae-principals/lib/delete'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import * as DisableUsersMigration from '../../../etc/migration/disable_users_from_tenancy/lib/disable-users-by-tenancy'; describe('Principals Delete and Restore', () => { // Rest context that can be used to perform requests as different types of users @@ -512,8 +509,8 @@ describe('Principals Delete and Restore', () => { */ it('verify deleting and restoring multiple times re-invokes handler logic', callback => { /*! - * Create a group delete handler that maintains the count of times it has been invoked - */ + * Create a group delete handler that maintains the count of times it has been invoked + */ let deleteHandlerCount = 0; PrincipalsDelete.registerGroupDeleteHandler( 'test-group-reinvoke', @@ -524,8 +521,8 @@ describe('Principals Delete and Restore', () => { ); /*! - * Create a group restore handler that maintains the count of times it has been invoked - */ + * Create a group restore handler that maintains the count of times it has been invoked + */ let restoreHandlerCount = 0; PrincipalsDelete.registerGroupRestoreHandler( 'test-group-reinvoke', diff --git a/packages/oae-principals/tests/test-emails.js b/packages/oae-principals/tests/test-emails.js index 3e06f39add..ac98660e0f 100644 --- a/packages/oae-principals/tests/test-emails.js +++ b/packages/oae-principals/tests/test-emails.js @@ -13,21 +13,19 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const util = require('util'); -const _ = require('underscore'); - -const AuthenticationTestUtil = require('oae-authentication/lib/test/util'); -const AuthzInvitationsDAO = require('oae-authz/lib/invitations/dao'); -const ConfigTestUtil = require('oae-config/lib/test/util'); -const EmailTestsUtil = require('oae-email/lib/test/util'); -const RestAPI = require('oae-rest'); -const TenantsTestUtil = require('oae-tenants/lib/test/util'); -const TestsUtil = require('oae-tests'); - -const PrincipalsAPI = require('oae-principals'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); +import assert from 'assert'; +import fs from 'fs'; +import util from 'util'; +import _ from 'underscore'; + +import * as AuthenticationTestUtil from 'oae-authentication/lib/test/util'; +import * as AuthzInvitationsDAO from 'oae-authz/lib/invitations/dao'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as EmailTestsUtil from 'oae-email/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; +import * as TestsUtil from 'oae-tests'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; describe('User emails', () => { // REST contexts we can use to do REST requests @@ -58,15 +56,10 @@ describe('User emails', () => { after(callback => { // Re-enable reCaptcha - ConfigTestUtil.updateConfigAndWait( - camAdminRestContext, - null, - { 'oae-principals/recaptcha/enabled': true }, - err => { - assert.ok(!err); - return callback(); - } - ); + ConfigTestUtil.updateConfigAndWait(camAdminRestContext, null, { 'oae-principals/recaptcha/enabled': true }, err => { + assert.ok(!err); + return callback(); + }); }); describe('Verification', () => { @@ -75,10 +68,7 @@ describe('User emails', () => { */ it('verify validation when verifying an email address', callback => { // We can't use TestsUtil.generateTestUsers as that would do the verification process for us - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); const params = { username: TestsUtil.generateTestUserId(), password: 'password', @@ -87,61 +77,33 @@ describe('User emails', () => { }; const restContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); PrincipalsTestUtil.assertCreateUserSucceeds(restContext, params, (user, token) => { - AuthenticationTestUtil.assertLocalLoginSucceeds( - restContext, - params.username, - params.password, - () => { - // Invalid user id - PrincipalsTestUtil.assertVerifyEmailFails( - restContext, - 'not a user id', - token, - 400, - () => { - // Missing token - PrincipalsTestUtil.assertVerifyEmailFails(restContext, user.id, null, 400, () => { - // Invalid token - PrincipalsTestUtil.assertVerifyEmailFails( - restContext, - user.id, - 'more than [7-14] characters', - 400, - () => { - // Incorrect token - PrincipalsTestUtil.assertVerifyEmailFails( - restContext, - user.id, - '123456789', - 401, - () => { - // Sanity-check - PrincipalsTestUtil.assertVerifyEmailSucceeds( - restContext, - user.id, - token, - () => { - // A token can only be verified once - PrincipalsTestUtil.assertVerifyEmailFails( - restContext, - user.id, - token, - 404, - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); - }); - } - ); - } - ); + AuthenticationTestUtil.assertLocalLoginSucceeds(restContext, params.username, params.password, () => { + // Invalid user id + PrincipalsTestUtil.assertVerifyEmailFails(restContext, 'not a user id', token, 400, () => { + // Missing token + PrincipalsTestUtil.assertVerifyEmailFails(restContext, user.id, null, 400, () => { + // Invalid token + PrincipalsTestUtil.assertVerifyEmailFails( + restContext, + user.id, + 'more than [7-14] characters', + 400, + () => { + // Incorrect token + PrincipalsTestUtil.assertVerifyEmailFails(restContext, user.id, '123456789', 401, () => { + // Sanity-check + PrincipalsTestUtil.assertVerifyEmailSucceeds(restContext, user.id, token, () => { + // A token can only be verified once + PrincipalsTestUtil.assertVerifyEmailFails(restContext, user.id, token, 404, () => { + return callback(); + }); + }); + }); + } + ); + }); + }); + }); }); }); @@ -150,10 +112,7 @@ describe('User emails', () => { */ it('verify authorization when verifying an email address', callback => { // We can't use TestsUtil.generateTestUsers as that would do the verification process for us - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); const params = { username: TestsUtil.generateTestUserId(), password: 'password', @@ -167,39 +126,17 @@ describe('User emails', () => { // Other users cannot verify an email address TestsUtil.generateTestUsers(camAdminRestContext, 1, (err, users, badGuy) => { assert.ok(!err); - PrincipalsTestUtil.assertVerifyEmailFails( - badGuy.restContext, - user.id, - token, - 401, - () => { - // Tenant admins from other tenants cannot verify an email address - PrincipalsTestUtil.assertVerifyEmailFails( - gtAdminRestContext, - user.id, - token, - 401, - () => { - // The user can verify their email address if they've signed in - AuthenticationTestUtil.assertLocalLoginSucceeds( - restContext, - params.username, - params.password, - () => { - PrincipalsTestUtil.assertVerifyEmailSucceeds( - restContext, - user.id, - token, - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); + PrincipalsTestUtil.assertVerifyEmailFails(badGuy.restContext, user.id, token, 401, () => { + // Tenant admins from other tenants cannot verify an email address + PrincipalsTestUtil.assertVerifyEmailFails(gtAdminRestContext, user.id, token, 401, () => { + // The user can verify their email address if they've signed in + AuthenticationTestUtil.assertLocalLoginSucceeds(restContext, params.username, params.password, () => { + PrincipalsTestUtil.assertVerifyEmailSucceeds(restContext, user.id, token, () => { + return callback(); + }); + }); + }); + }); }); }); }); @@ -217,54 +154,25 @@ describe('User emails', () => { email: TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]) }; const restContextUser1 = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); - PrincipalsTestUtil.assertCreateUserSucceeds( - restContextUser1, - paramsUser1, - (user1, tokenUser1) => { - AuthenticationTestUtil.assertLocalLoginSucceeds( - restContextUser1, - paramsUser1.username, - 'password', - () => { - const paramsUser2 = { - username: TestsUtil.generateTestUserId(), - password: 'password', - displayName: TestsUtil.generateRandomText(1), - email: TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ) - }; - const restContextUser2 = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.cam.host - ); - PrincipalsTestUtil.assertCreateUserSucceeds( - restContextUser2, - paramsUser2, - (user2, tokenUser2) => { - AuthenticationTestUtil.assertLocalLoginSucceeds( - restContextUser2, - paramsUser2.username, - 'password', - () => { - // Assert we cannot user the token from the first user - PrincipalsTestUtil.assertVerifyEmailFails( - restContextUser2, - user2.id, - tokenUser1, - 401, - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + PrincipalsTestUtil.assertCreateUserSucceeds(restContextUser1, paramsUser1, (user1, tokenUser1) => { + AuthenticationTestUtil.assertLocalLoginSucceeds(restContextUser1, paramsUser1.username, 'password', () => { + const paramsUser2 = { + username: TestsUtil.generateTestUserId(), + password: 'password', + displayName: TestsUtil.generateRandomText(1), + email: TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]) + }; + const restContextUser2 = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); + PrincipalsTestUtil.assertCreateUserSucceeds(restContextUser2, paramsUser2, (user2, tokenUser2) => { + AuthenticationTestUtil.assertLocalLoginSucceeds(restContextUser2, paramsUser2.username, 'password', () => { + // Assert we cannot user the token from the first user + PrincipalsTestUtil.assertVerifyEmailFails(restContextUser2, user2.id, tokenUser1, 401, () => { + return callback(); + }); + }); + }); + }); + }); }); /** @@ -273,85 +181,64 @@ describe('User emails', () => { it('verify an email address can be re-used by other users', callback => { const tenantAlias = TenantsTestUtil.generateTestTenantAlias(); const tenantHost = TenantsTestUtil.generateTestTenantHost(); - TestsUtil.createTenantWithAdmin( - tenantAlias, - tenantHost, - (err, tenant, tenantAdminRestContext, tenantAdmin) => { - assert.ok(!err); + TestsUtil.createTenantWithAdmin(tenantAlias, tenantHost, (err, tenant, tenantAdminRestContext, tenantAdmin) => { + assert.ok(!err); - // Disable reCaptcha for this tenant - ConfigTestUtil.updateConfigAndWait( - globalAdminRestContext, - tenantAlias, - { 'oae-principals/recaptcha/enabled': false }, - err => { - assert.ok(!err); + // Disable reCaptcha for this tenant + ConfigTestUtil.updateConfigAndWait( + globalAdminRestContext, + tenantAlias, + { 'oae-principals/recaptcha/enabled': false }, + err => { + assert.ok(!err); - const username1 = TestsUtil.generateTestUserId(); - const username2 = TestsUtil.generateTestUserId(); - const email = TestsUtil.generateTestEmailAddress(null, tenantHost); - const paramsUser1 = { - displayName: 'Test user 1', - email, - password: 'password', - username: TestsUtil.generateTestUserId() - }; - const paramsUser2 = { - displayName: 'Test user 2', - email, - password: 'password', - username: TestsUtil.generateTestUserId() - }; - - // Create the first user as a tenant admin so the email address is considered verified - PrincipalsTestUtil.assertCreateUserSucceeds( - tenantAdminRestContext, - paramsUser1, - user1 => { - // Verify there's a mapping for the first user + const username1 = TestsUtil.generateTestUserId(); + const username2 = TestsUtil.generateTestUserId(); + const email = TestsUtil.generateTestEmailAddress(null, tenantHost); + const paramsUser1 = { + displayName: 'Test user 1', + email, + password: 'password', + username: TestsUtil.generateTestUserId() + }; + const paramsUser2 = { + displayName: 'Test user 2', + email, + password: 'password', + username: TestsUtil.generateTestUserId() + }; + + // Create the first user as a tenant admin so the email address is considered verified + PrincipalsTestUtil.assertCreateUserSucceeds(tenantAdminRestContext, paramsUser1, user1 => { + // Verify there's a mapping for the first user + PrincipalsTestUtil.assertUserEmailMappingEquals(email, [user1.id], () => { + // Create the second user as an anonymous user so the email address is not verified + const restContext = TestsUtil.createTenantRestContext(tenantHost); + PrincipalsTestUtil.assertCreateUserSucceeds(restContext, paramsUser2, (user2, token) => { + // Verify there's no mapping yet for the second user as the email address + // hasn't been verified yet PrincipalsTestUtil.assertUserEmailMappingEquals(email, [user1.id], () => { - // Create the second user as an anonymous user so the email address is not verified - const restContext = TestsUtil.createTenantRestContext(tenantHost); - PrincipalsTestUtil.assertCreateUserSucceeds( + // Verify the email address + AuthenticationTestUtil.assertLocalLoginSucceeds( restContext, - paramsUser2, - (user2, token) => { - // Verify there's no mapping yet for the second user as the email address - // hasn't been verified yet - PrincipalsTestUtil.assertUserEmailMappingEquals(email, [user1.id], () => { - // Verify the email address - AuthenticationTestUtil.assertLocalLoginSucceeds( - restContext, - paramsUser2.username, - paramsUser2.password, - () => { - PrincipalsTestUtil.assertVerifyEmailSucceeds( - restContext, - user2.id, - token, - () => { - // The second user should now also be mapped to the email address - PrincipalsTestUtil.assertUserEmailMappingEquals( - email, - [user1.id, user2.id], - () => { - return callback(); - } - ); - } - ); - } - ); + paramsUser2.username, + paramsUser2.password, + () => { + PrincipalsTestUtil.assertVerifyEmailSucceeds(restContext, user2.id, token, () => { + // The second user should now also be mapped to the email address + PrincipalsTestUtil.assertUserEmailMappingEquals(email, [user1.id, user2.id], () => { + return callback(); + }); }); } ); }); - } - ); - } - ); - } - ); + }); + }); + }); + } + ); + }); }); /** @@ -359,10 +246,7 @@ describe('User emails', () => { */ it('verify local user accounts need to verify their email address', callback => { // We can't use TestsUtil.generateTestUsers as that would do the verification process for us - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); const params = { username: TestsUtil.generateTestUserId(), password: 'password', @@ -379,19 +263,14 @@ describe('User emails', () => { PrincipalsTestUtil.assertUserEmailMappingEquals(email, [], () => { // Verify the email address const restContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); - AuthenticationTestUtil.assertLocalLoginSucceeds( - restContext, - params.username, - params.password, - () => { - PrincipalsTestUtil.assertVerifyEmailSucceeds(restContext, user.id, token, () => { - // Assert the mapping has been created - PrincipalsTestUtil.assertUserEmailMappingEquals(email, [user.id], () => { - return callback(); - }); + AuthenticationTestUtil.assertLocalLoginSucceeds(restContext, params.username, params.password, () => { + PrincipalsTestUtil.assertVerifyEmailSucceeds(restContext, user.id, token, () => { + // Assert the mapping has been created + PrincipalsTestUtil.assertUserEmailMappingEquals(email, [user.id], () => { + return callback(); }); - } - ); + }); + }); }); }); }); @@ -402,10 +281,7 @@ describe('User emails', () => { */ it('verify activity emails are not sent to unverified email addresses', callback => { // We can't use TestsUtil.generateTestUsers as that would do the verification process for us - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); const params = { username: TestsUtil.generateTestUserId(), password: 'password', @@ -447,10 +323,7 @@ describe('User emails', () => { */ it('verify local user accounts created by a tenant administrator do not need to verify their email address', callback => { // We can't use TestsUtil.generateTestUsers as that would do the verification process for us - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); const params = { username: TestsUtil.generateTestUserId(), password: 'password', @@ -481,73 +354,60 @@ describe('User emails', () => { // Create a tenant and enable Facebook authentication on it const tenantAlias = TenantsTestUtil.generateTestTenantAlias(); const tenantHost = TenantsTestUtil.generateTestTenantHost(); - TestsUtil.createTenantWithAdmin( - tenantAlias, - tenantHost, - (err, tenant, tenantAdminRestContext) => { - assert.ok(!err); - AuthenticationTestUtil.assertUpdateAuthConfigSucceeds( - tenantAdminRestContext, - null, - { 'oae-authentication/facebook/enabled': true }, - () => { - // Remove the tenant's email domain so we can sign in through Facebook - TenantsTestUtil.updateTenantAndWait( - globalAdminRestContext, - tenant.alias, - { emailDomains: '' }, - () => { - // First make sure authenticating without email and no - // invitation results in a user without an email - AuthenticationTestUtil.assertFacebookLoginSucceeds( - tenant.host, - null, - (restContext, me) => { - assert.ok(!me.email); - - // Create an invitation - let email = TestsUtil.generateTestEmailAddress(null, tenant.emailDomains[0]); - PrincipalsTestUtil.assertCreateGroupSucceeds( - tenantAdminRestContext, - 'My Group', - 'My description', - 'public', - 'yes', - [email], - null, - group => { - email = email.toLowerCase(); - - // Get the invitation info so we can make a redirect url - AuthzInvitationsDAO.getTokensByEmails([email], (err, tokensByEmail) => { - assert.ok(!err); - const token = tokensByEmail[email]; - - // Authenticate without an email while following the redirect url - const redirectUrl = util.format( - '/signup?invitationToken=%s&invitationEmail=%s', - encodeURIComponent(token), - encodeURIComponent(email) - ); - AuthenticationTestUtil.assertFacebookLoginSucceeds( - tenant.host, - { redirectUrl }, - (restContext, me) => { - assert.strictEqual(me.email, email); - return callback(); - } - ); - }); + TestsUtil.createTenantWithAdmin(tenantAlias, tenantHost, (err, tenant, tenantAdminRestContext) => { + assert.ok(!err); + AuthenticationTestUtil.assertUpdateAuthConfigSucceeds( + tenantAdminRestContext, + null, + { 'oae-authentication/facebook/enabled': true }, + () => { + // Remove the tenant's email domain so we can sign in through Facebook + TenantsTestUtil.updateTenantAndWait(globalAdminRestContext, tenant.alias, { emailDomains: '' }, () => { + // First make sure authenticating without email and no + // invitation results in a user without an email + AuthenticationTestUtil.assertFacebookLoginSucceeds(tenant.host, null, (restContext, me) => { + assert.ok(!me.email); + + // Create an invitation + let email = TestsUtil.generateTestEmailAddress(null, tenant.emailDomains[0]); + PrincipalsTestUtil.assertCreateGroupSucceeds( + tenantAdminRestContext, + 'My Group', + 'My description', + 'public', + 'yes', + [email], + null, + group => { + email = email.toLowerCase(); + + // Get the invitation info so we can make a redirect url + AuthzInvitationsDAO.getTokensByEmails([email], (err, tokensByEmail) => { + assert.ok(!err); + const token = tokensByEmail[email]; + + // Authenticate without an email while following the redirect url + const redirectUrl = util.format( + '/signup?invitationToken=%s&invitationEmail=%s', + encodeURIComponent(token), + encodeURIComponent(email) + ); + AuthenticationTestUtil.assertFacebookLoginSucceeds( + tenant.host, + { redirectUrl }, + (restContext, me) => { + assert.strictEqual(me.email, email); + return callback(); } ); - } - ); - } - ); - } - ); - } - ); + }); + } + ); + }); + }); + } + ); + }); }); /** @@ -557,40 +417,32 @@ describe('User emails', () => { // Create a tenant and enable google authentication on it const tenantAlias = TenantsTestUtil.generateTestTenantAlias(); const tenantHost = TenantsTestUtil.generateTestTenantHost(); - TestsUtil.createTenantWithAdmin( - tenantAlias, - tenantHost, - (err, tenant, tenantAdminRestContext) => { - assert.ok(!err); - AuthenticationTestUtil.assertUpdateAuthConfigSucceeds( - tenantAdminRestContext, - null, - { 'oae-authentication/google/enabled': true }, - () => { - // Sign in through google - const email = TestsUtil.generateTestEmailAddress(null, tenant.emailDomains[0]); - AuthenticationTestUtil.assertGoogleLoginSucceeds( - tenant.host, - email, - (restContext, response) => { - // As google is considered an authoritative source, the user shouldn't have - // to verify their email address - RestAPI.User.getMe(restContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.email, email.toLowerCase()); + TestsUtil.createTenantWithAdmin(tenantAlias, tenantHost, (err, tenant, tenantAdminRestContext) => { + assert.ok(!err); + AuthenticationTestUtil.assertUpdateAuthConfigSucceeds( + tenantAdminRestContext, + null, + { 'oae-authentication/google/enabled': true }, + () => { + // Sign in through google + const email = TestsUtil.generateTestEmailAddress(null, tenant.emailDomains[0]); + AuthenticationTestUtil.assertGoogleLoginSucceeds(tenant.host, email, (restContext, response) => { + // As google is considered an authoritative source, the user shouldn't have + // to verify their email address + RestAPI.User.getMe(restContext, (err, me) => { + assert.ok(!err); + assert.ok(!me.anon); + assert.strictEqual(me.email, email.toLowerCase()); - // Assert that there's a mapping for the email address - PrincipalsTestUtil.assertUserEmailMappingEquals(email, [me.id], () => { - return callback(); - }); - }); - } - ); - } - ); - } - ); + // Assert that there's a mapping for the email address + PrincipalsTestUtil.assertUserEmailMappingEquals(email, [me.id], () => { + return callback(); + }); + }); + }); + } + ); + }); }); /** @@ -601,69 +453,65 @@ describe('User emails', () => { // Create a tenant in which to create a user, and ensure there are no pending emails const tenantAlias = TenantsTestUtil.generateTestTenantAlias(); const tenantHost = TenantsTestUtil.generateTestTenantHost(); - TestsUtil.createTenantWithAdmin( - tenantAlias, - tenantHost, - (err, tenant, tenantAdminRestContext) => { - assert.ok(!err); - - // Create 2 email addresses that have tokens associated to them - const email1 = TestsUtil.generateTestEmailAddress(null, tenant.emailDomains[0]); - const email2 = TestsUtil.generateTestEmailAddress(null, tenant.emailDomains[0]); - AuthzInvitationsDAO.getOrCreateTokensByEmails([email1, email2], (err, emailTokens) => { - assert.ok(!err); + TestsUtil.createTenantWithAdmin(tenantAlias, tenantHost, (err, tenant, tenantAdminRestContext) => { + assert.ok(!err); - // Try to create a profile with email1, using email2's token. It should - // work, but it shouldn't auto-verify the email address - const profile = { - username: TestsUtil.generateTestUserId(), - password: 'password', - displayName: TestsUtil.generateRandomText(), - email: email1, - invitationToken: emailTokens[email2] - }; + // Create 2 email addresses that have tokens associated to them + const email1 = TestsUtil.generateTestEmailAddress(null, tenant.emailDomains[0]); + const email2 = TestsUtil.generateTestEmailAddress(null, tenant.emailDomains[0]); + AuthzInvitationsDAO.getOrCreateTokensByEmails([email1, email2], (err, emailTokens) => { + assert.ok(!err); - // Ensure we get a user with an unverified email, as well as one email which - // is a verification email for email1 - EmailTestsUtil.startCollectingEmail(stopCollectingEmail => { - PrincipalsTestUtil.assertCreateUserSucceeds( - TestsUtil.createTenantRestContext(tenantHost), - profile, - user => { - assert.ok(!user.email); - - stopCollectingEmail(messages => { - assert.strictEqual(_.size(messages), 1); - - // Now create a profile with the matching email1 token and ensure - // email is automatically verified - _.extend(profile, { - username: TestsUtil.generateTestUserId(), - invitationToken: emailTokens[email1] - }); + // Try to create a profile with email1, using email2's token. It should + // work, but it shouldn't auto-verify the email address + const profile = { + username: TestsUtil.generateTestUserId(), + password: 'password', + displayName: TestsUtil.generateRandomText(), + email: email1, + invitationToken: emailTokens[email2] + }; + + // Ensure we get a user with an unverified email, as well as one email which + // is a verification email for email1 + EmailTestsUtil.startCollectingEmail(stopCollectingEmail => { + PrincipalsTestUtil.assertCreateUserSucceeds( + TestsUtil.createTenantRestContext(tenantHost), + profile, + user => { + assert.ok(!user.email); + + stopCollectingEmail(messages => { + assert.strictEqual(_.size(messages), 1); + + // Now create a profile with the matching email1 token and ensure + // email is automatically verified + _.extend(profile, { + username: TestsUtil.generateTestUserId(), + invitationToken: emailTokens[email1] + }); - EmailTestsUtil.startCollectingEmail(stopCollectingEmail => { - // We should get a profile with a verified email and no verification - // email - PrincipalsTestUtil.assertCreateUserSucceeds( - TestsUtil.createTenantRestContext(tenantHost), - profile, - user => { - assert.strictEqual(user.email, email1.toLowerCase()); - stopCollectingEmail(messages => { - assert.ok(_.isEmpty(messages)); - return callback(); - }); - } - ); - }); + EmailTestsUtil.startCollectingEmail(stopCollectingEmail => { + // We should get a profile with a verified email and no verification + // email + PrincipalsTestUtil.assertCreateUserSucceeds( + TestsUtil.createTenantRestContext(tenantHost), + profile, + user => { + assert.strictEqual(user.email, email1.toLowerCase()); + stopCollectingEmail(messages => { + assert.ok(_.isEmpty(messages)); + return callback(); + }); + } + ); }); - } - ); - }); + }); + } + ); }); - } - ); + }); + }); }); /** @@ -684,76 +532,56 @@ describe('User emails', () => { it('verify user accounts created or updated through a CSV import do not need to verify their email address', callback => { const tenantAlias = TenantsTestUtil.generateTestTenantAlias(); const tenantHost = 'users.emails.com'; - TestsUtil.createTenantWithAdmin( - tenantAlias, - tenantHost, - (err, tenant, tenantAdminRestContext) => { - assert.ok(!err); + TestsUtil.createTenantWithAdmin(tenantAlias, tenantHost, (err, tenant, tenantAdminRestContext) => { + assert.ok(!err); - // Import users as a global admin using a local authentication strategy - PrincipalsTestUtil.importUsers( - globalAdminRestContext, - tenant.alias, - getDataFileStream('users-emails.csv'), - 'local', - null, - err => { + // Import users as a global admin using a local authentication strategy + PrincipalsTestUtil.importUsers( + globalAdminRestContext, + tenant.alias, + getDataFileStream('users-emails.csv'), + 'local', + null, + err => { + assert.ok(!err); + + // Verify the user's email address is verified + const restContext = TestsUtil.createTenantRestContext(tenant.host, 'users-emails-abc123', 'password'); + RestAPI.User.getMe(restContext, (err, user) => { assert.ok(!err); + assert.strictEqual(user.email, 'foo@users.emails.com'); + + // Assert there's a mapping for the email address + PrincipalsTestUtil.assertUserEmailMappingEquals('foo@users.emails.com', [user.id], () => { + // Update the email address through a CSV import + PrincipalsTestUtil.importUsers( + globalAdminRestContext, + tenant.alias, + getDataFileStream('users-emails-updated.csv'), + 'local', + true, + err => { + assert.ok(!err); - // Verify the user's email address is verified - const restContext = TestsUtil.createTenantRestContext( - tenant.host, - 'users-emails-abc123', - 'password' - ); - RestAPI.User.getMe(restContext, (err, user) => { - assert.ok(!err); - assert.strictEqual(user.email, 'foo@users.emails.com'); - - // Assert there's a mapping for the email address - PrincipalsTestUtil.assertUserEmailMappingEquals( - 'foo@users.emails.com', - [user.id], - () => { - // Update the email address through a CSV import - PrincipalsTestUtil.importUsers( - globalAdminRestContext, - tenant.alias, - getDataFileStream('users-emails-updated.csv'), - 'local', - true, - err => { - assert.ok(!err); - - RestAPI.User.getMe(restContext, (err, user) => { - assert.ok(!err); - assert.strictEqual(user.email, 'bar@users.emails.com'); - - // Assert there's a mapping for the new email address - PrincipalsTestUtil.assertUserEmailMappingEquals( - 'bar@users.emails.com', - [user.id], - () => { - // Assert there's no mapping for the old email address - PrincipalsTestUtil.assertUserEmailMappingEquals( - 'foo@users.emails.com', - [], - () => { - return callback(); - } - ); - } - ); + RestAPI.User.getMe(restContext, (err, user) => { + assert.ok(!err); + assert.strictEqual(user.email, 'bar@users.emails.com'); + + // Assert there's a mapping for the new email address + PrincipalsTestUtil.assertUserEmailMappingEquals('bar@users.emails.com', [user.id], () => { + // Assert there's no mapping for the old email address + PrincipalsTestUtil.assertUserEmailMappingEquals('foo@users.emails.com', [], () => { + return callback(); }); - } - ); + }); + }); } ); }); - } - ); - } - ); + }); + } + ); + }); }); /** @@ -772,10 +600,7 @@ describe('User emails', () => { // Sanity-check there's a mapping for it PrincipalsTestUtil.assertUserEmailMappingEquals(me.email, [me.id], () => { // Update the email address - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); PrincipalsTestUtil.assertUpdateUserSucceeds( simong.restContext, simong.user.id, @@ -791,33 +616,20 @@ describe('User emails', () => { assert.strictEqual(me.email, oldEmailAddress); // Assert we can verify the email address - PrincipalsTestUtil.assertVerifyEmailSucceeds( - simong.restContext, - simong.user.id, - token, - () => { - // Assert the old mapping is gone - PrincipalsTestUtil.assertUserEmailMappingEquals( - oldEmailAddress, - [], - () => { - // Assert the new mapping is there - PrincipalsTestUtil.assertUserEmailMappingEquals( - email, - [me.id], - () => { - // The new email address should be confirmed - RestAPI.User.getMe(simong.restContext, (err, me) => { - assert.ok(!err); - assert.strictEqual(me.email, email.toLowerCase()); - return callback(); - }); - } - ); - } - ); - } - ); + PrincipalsTestUtil.assertVerifyEmailSucceeds(simong.restContext, simong.user.id, token, () => { + // Assert the old mapping is gone + PrincipalsTestUtil.assertUserEmailMappingEquals(oldEmailAddress, [], () => { + // Assert the new mapping is there + PrincipalsTestUtil.assertUserEmailMappingEquals(email, [me.id], () => { + // The new email address should be confirmed + RestAPI.User.getMe(simong.restContext, (err, me) => { + assert.ok(!err); + assert.strictEqual(me.email, email.toLowerCase()); + return callback(); + }); + }); + }); + }); }); }); }); @@ -837,20 +649,12 @@ describe('User emails', () => { const oldEmailAddress = simong.user.email; // Let an administrator update the email address - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); - PrincipalsTestUtil.assertUpdateUserSucceeds( - camAdminRestContext, - simong.user.id, - { email }, - (user, token) => { - // The email address still has to be verified - assert.strictEqual(user.email, oldEmailAddress); - return callback(); - } - ); + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); + PrincipalsTestUtil.assertUpdateUserSucceeds(camAdminRestContext, simong.user.id, { email }, (user, token) => { + // The email address still has to be verified + assert.strictEqual(user.email, oldEmailAddress); + return callback(); + }); }); }); @@ -863,61 +667,40 @@ describe('User emails', () => { const oldEmailAddress = simong.user.email; // Update the email address for the first time - const email1 = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); + const email1 = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); PrincipalsTestUtil.assertUpdateUserSucceeds( simong.restContext, simong.user.id, { email: email1 }, (user, token1) => { // Update the email address again (with a second email address) - const email2 = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); + const email2 = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); PrincipalsTestUtil.assertUpdateUserSucceeds( simong.restContext, simong.user.id, { email: email2 }, (user, token2) => { // Using the first token to verify the email address should fail - PrincipalsTestUtil.assertVerifyEmailFails( - simong.restContext, - simong.user.id, - token1, - 401, - () => { - // Using the second token we can verify the email address - PrincipalsTestUtil.assertVerifyEmailSucceeds( - simong.restContext, - simong.user.id, - token2, - () => { - // Assert the old mapping is gone - PrincipalsTestUtil.assertUserEmailMappingEquals(oldEmailAddress, [], () => { - // Assert no mapping was created for the first email address - PrincipalsTestUtil.assertUserEmailMappingEquals(email1, [], () => { - // Assert a mapping for the second email address is created - PrincipalsTestUtil.assertUserEmailMappingEquals( - email2, - [simong.user.id], - () => { - // The second email address should be confirmed - RestAPI.User.getMe(simong.restContext, (err, me) => { - assert.ok(!err); - assert.strictEqual(me.email, email2.toLowerCase()); - return callback(); - }); - } - ); + PrincipalsTestUtil.assertVerifyEmailFails(simong.restContext, simong.user.id, token1, 401, () => { + // Using the second token we can verify the email address + PrincipalsTestUtil.assertVerifyEmailSucceeds(simong.restContext, simong.user.id, token2, () => { + // Assert the old mapping is gone + PrincipalsTestUtil.assertUserEmailMappingEquals(oldEmailAddress, [], () => { + // Assert no mapping was created for the first email address + PrincipalsTestUtil.assertUserEmailMappingEquals(email1, [], () => { + // Assert a mapping for the second email address is created + PrincipalsTestUtil.assertUserEmailMappingEquals(email2, [simong.user.id], () => { + // The second email address should be confirmed + RestAPI.User.getMe(simong.restContext, (err, me) => { + assert.ok(!err); + assert.strictEqual(me.email, email2.toLowerCase()); + return callback(); }); }); - } - ); - } - ); + }); + }); + }); + }); } ); } @@ -932,10 +715,7 @@ describe('User emails', () => { */ it('verify an email verification token can be resent', callback => { // We can't use TestsUtil.generateTestUsers as that would do the verification process for us - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); const params = { username: TestsUtil.generateTestUserId(), password: 'password', @@ -966,10 +746,7 @@ describe('User emails', () => { */ it('verify validation when resending an email verification token', callback => { // We can't use TestsUtil.generateTestUsers as that would do the verification process for us - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); const params = { username: TestsUtil.generateTestUserId(), password: 'password', @@ -994,10 +771,7 @@ describe('User emails', () => { */ it('verify authorization when resending an email verification token', callback => { // We can't use TestsUtil.generateTestUsers as that would do the verification process for us - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); const params = { username: TestsUtil.generateTestUserId(), password: 'password', @@ -1014,36 +788,18 @@ describe('User emails', () => { // Users cannot resend a token for someone else TestsUtil.generateTestUsers(camAdminRestContext, 1, (err, users, otherUser) => { assert.ok(!err); - PrincipalsTestUtil.assertResendEmailTokenFails( - otherUser.restContext, - user.id, - 401, - () => { - // Tenant administrators from another tenant cannot resend a token - PrincipalsTestUtil.assertResendEmailTokenFails( - gtAdminRestContext, - user.id, - 401, - () => { - // Tenant administrators from the same tenant as the user can resend a token - PrincipalsTestUtil.assertResendEmailTokenSucceeds( - camAdminRestContext, - user.id, - newToken => { - // Global administrators can resend a token - PrincipalsTestUtil.assertResendEmailTokenSucceeds( - globalAdminRestContext, - user.id, - newToken => { - return callback(); - } - ); - } - ); - } - ); - } - ); + PrincipalsTestUtil.assertResendEmailTokenFails(otherUser.restContext, user.id, 401, () => { + // Tenant administrators from another tenant cannot resend a token + PrincipalsTestUtil.assertResendEmailTokenFails(gtAdminRestContext, user.id, 401, () => { + // Tenant administrators from the same tenant as the user can resend a token + PrincipalsTestUtil.assertResendEmailTokenSucceeds(camAdminRestContext, user.id, newToken => { + // Global administrators can resend a token + PrincipalsTestUtil.assertResendEmailTokenSucceeds(globalAdminRestContext, user.id, newToken => { + return callback(); + }); + }); + }); + }); }); }); }); @@ -1059,49 +815,22 @@ describe('User emails', () => { assert.ok(!err); // Resending a token should fail as there is no token to resend - PrincipalsTestUtil.assertResendEmailTokenFails( - simong.restContext, - simong.user.id, - 404, - () => { - // Update the email address so we get a token - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); - PrincipalsTestUtil.assertUpdateUserSucceeds( - simong.restContext, - simong.user.id, - { email }, - (user, token) => { - // Resending a token now should be OK - PrincipalsTestUtil.assertResendEmailTokenSucceeds( - simong.restContext, - simong.user.id, - newToken => { - // Use the token to verify the new email address - PrincipalsTestUtil.assertVerifyEmailSucceeds( - simong.restContext, - simong.user.id, - newToken, - () => { - // Resending a token should now fail as the token has been used and should be removed - PrincipalsTestUtil.assertResendEmailTokenFails( - simong.restContext, - simong.user.id, - 404, - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + PrincipalsTestUtil.assertResendEmailTokenFails(simong.restContext, simong.user.id, 404, () => { + // Update the email address so we get a token + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); + PrincipalsTestUtil.assertUpdateUserSucceeds(simong.restContext, simong.user.id, { email }, (user, token) => { + // Resending a token now should be OK + PrincipalsTestUtil.assertResendEmailTokenSucceeds(simong.restContext, simong.user.id, newToken => { + // Use the token to verify the new email address + PrincipalsTestUtil.assertVerifyEmailSucceeds(simong.restContext, simong.user.id, newToken, () => { + // Resending a token should now fail as the token has been used and should be removed + PrincipalsTestUtil.assertResendEmailTokenFails(simong.restContext, simong.user.id, 404, () => { + return callback(); + }); + }); + }); + }); + }); }); }); }); @@ -1115,36 +844,20 @@ describe('User emails', () => { assert.ok(!err); // Verify no email is returned when the user has no pending verification - PrincipalsTestUtil.assertGetEmailTokenSucceeds( - simong.restContext, - simong.user.id, - email => { - assert.ok(!email); + PrincipalsTestUtil.assertGetEmailTokenSucceeds(simong.restContext, simong.user.id, email => { + assert.ok(!email); - // Update the user's email address - email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); - PrincipalsTestUtil.assertUpdateUserSucceeds( - simong.restContext, - simong.user.id, - { email }, - () => { - // Verify the user as a token - PrincipalsTestUtil.assertGetEmailTokenSucceeds( - simong.restContext, - simong.user.id, - emailForToken => { - assert.strictEqual(emailForToken, email.toLowerCase()); + // Update the user's email address + email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); + PrincipalsTestUtil.assertUpdateUserSucceeds(simong.restContext, simong.user.id, { email }, () => { + // Verify the user as a token + PrincipalsTestUtil.assertGetEmailTokenSucceeds(simong.restContext, simong.user.id, emailForToken => { + assert.strictEqual(emailForToken, email.toLowerCase()); - return callback(); - } - ); - } - ); - } - ); + return callback(); + }); + }); + }); }); }); @@ -1156,21 +869,11 @@ describe('User emails', () => { assert.ok(!err); // Invalid user id - PrincipalsTestUtil.assertGetEmailTokenFails( - simong.restContext, - 'not a user id', - 400, - () => { - PrincipalsTestUtil.assertGetEmailTokenFails( - simong.restContext, - 'g:camtest:1234234', - 400, - () => { - return callback(); - } - ); - } - ); + PrincipalsTestUtil.assertGetEmailTokenFails(simong.restContext, 'not a user id', 400, () => { + PrincipalsTestUtil.assertGetEmailTokenFails(simong.restContext, 'g:camtest:1234234', 400, () => { + return callback(); + }); + }); }); }); @@ -1182,56 +885,25 @@ describe('User emails', () => { assert.ok(!err); // Update simon's email address so he has a verification token - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); - PrincipalsTestUtil.assertUpdateUserSucceeds( - simong.restContext, - simong.user.id, - { email }, - () => { - // Anonymous users cannot check anything - PrincipalsTestUtil.assertGetEmailTokenFails( - anonymousRestContext, - simong.user.id, - 401, - () => { - // Users cannot check other users their email tokens - PrincipalsTestUtil.assertGetEmailTokenFails( - mrvisser.restContext, - simong.user.id, - 401, - () => { - // Tenant administrator cannot check users from another tenant their email token - PrincipalsTestUtil.assertGetEmailTokenFails( - gtAdminRestContext, - simong.user.id, - 401, - () => { - // A user can check his own pending email verification token - PrincipalsTestUtil.assertGetEmailTokenSucceeds( - simong.restContext, - simong.user.id, - emailForToken => { - // A tenant admin can check the pending email verification token for users of their tenant - PrincipalsTestUtil.assertGetEmailTokenSucceeds( - camAdminRestContext, - simong.user.id, - emailForToken => { - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); + PrincipalsTestUtil.assertUpdateUserSucceeds(simong.restContext, simong.user.id, { email }, () => { + // Anonymous users cannot check anything + PrincipalsTestUtil.assertGetEmailTokenFails(anonymousRestContext, simong.user.id, 401, () => { + // Users cannot check other users their email tokens + PrincipalsTestUtil.assertGetEmailTokenFails(mrvisser.restContext, simong.user.id, 401, () => { + // Tenant administrator cannot check users from another tenant their email token + PrincipalsTestUtil.assertGetEmailTokenFails(gtAdminRestContext, simong.user.id, 401, () => { + // A user can check his own pending email verification token + PrincipalsTestUtil.assertGetEmailTokenSucceeds(simong.restContext, simong.user.id, emailForToken => { + // A tenant admin can check the pending email verification token for users of their tenant + PrincipalsTestUtil.assertGetEmailTokenSucceeds(camAdminRestContext, simong.user.id, emailForToken => { + return callback(); + }); + }); + }); + }); + }); + }); }); }); }); @@ -1245,33 +917,16 @@ describe('User emails', () => { assert.ok(!err); // Update the email address - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); - PrincipalsTestUtil.assertUpdateUserSucceeds( - simong.restContext, - simong.user.id, - { email }, - () => { - // Delete the token - PrincipalsTestUtil.assertDeleteEmailTokenSucceeds( - simong.restContext, - simong.user.id, - () => { - // Verify a token can't be deleted twice - PrincipalsTestUtil.assertDeleteEmailTokenFails( - simong.restContext, - simong.user.id, - 404, - () => { - return callback(); - } - ); - } - ); - } - ); + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); + PrincipalsTestUtil.assertUpdateUserSucceeds(simong.restContext, simong.user.id, { email }, () => { + // Delete the token + PrincipalsTestUtil.assertDeleteEmailTokenSucceeds(simong.restContext, simong.user.id, () => { + // Verify a token can't be deleted twice + PrincipalsTestUtil.assertDeleteEmailTokenFails(simong.restContext, simong.user.id, 404, () => { + return callback(); + }); + }); + }); }); }); @@ -1283,29 +938,14 @@ describe('User emails', () => { assert.ok(!err); // Invalid user id - PrincipalsTestUtil.assertDeleteEmailTokenFails( - simong.restContext, - 'not a user id', - 400, - () => { - PrincipalsTestUtil.assertDeleteEmailTokenFails( - simong.restContext, - 'g:camtest:1234234', - 400, - () => { - // No token should return a 404 - PrincipalsTestUtil.assertDeleteEmailTokenFails( - simong.restContext, - simong.user.id, - 404, - () => { - return callback(); - } - ); - } - ); - } - ); + PrincipalsTestUtil.assertDeleteEmailTokenFails(simong.restContext, 'not a user id', 400, () => { + PrincipalsTestUtil.assertDeleteEmailTokenFails(simong.restContext, 'g:camtest:1234234', 400, () => { + // No token should return a 404 + PrincipalsTestUtil.assertDeleteEmailTokenFails(simong.restContext, simong.user.id, 404, () => { + return callback(); + }); + }); + }); }); }); @@ -1317,67 +957,32 @@ describe('User emails', () => { assert.ok(!err); // Update simon's email address so he has a verification token - let email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); - PrincipalsTestUtil.assertUpdateUserSucceeds( - simong.restContext, - simong.user.id, - { email }, - () => { - // Anonymous users cannot delete anything - PrincipalsTestUtil.assertDeleteEmailTokenFails( - anonymousRestContext, - simong.user.id, - 401, - () => { - // Users cannot delete other users their email tokens - PrincipalsTestUtil.assertDeleteEmailTokenFails( - mrvisser.restContext, - simong.user.id, - 401, - () => { - // Tenant administrator cannot delete users from another tenant their email token - PrincipalsTestUtil.assertDeleteEmailTokenFails( - gtAdminRestContext, + let email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); + PrincipalsTestUtil.assertUpdateUserSucceeds(simong.restContext, simong.user.id, { email }, () => { + // Anonymous users cannot delete anything + PrincipalsTestUtil.assertDeleteEmailTokenFails(anonymousRestContext, simong.user.id, 401, () => { + // Users cannot delete other users their email tokens + PrincipalsTestUtil.assertDeleteEmailTokenFails(mrvisser.restContext, simong.user.id, 401, () => { + // Tenant administrator cannot delete users from another tenant their email token + PrincipalsTestUtil.assertDeleteEmailTokenFails(gtAdminRestContext, simong.user.id, 401, () => { + // A user can delete his own pending email verification token + PrincipalsTestUtil.assertDeleteEmailTokenSucceeds(simong.restContext, simong.user.id, emailForToken => { + email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); + PrincipalsTestUtil.assertUpdateUserSucceeds(simong.restContext, simong.user.id, { email }, () => { + // A tenant admin can delete the pending email verification token for users of their tenant + PrincipalsTestUtil.assertDeleteEmailTokenSucceeds( + camAdminRestContext, simong.user.id, - 401, - () => { - // A user can delete his own pending email verification token - PrincipalsTestUtil.assertDeleteEmailTokenSucceeds( - simong.restContext, - simong.user.id, - emailForToken => { - email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); - PrincipalsTestUtil.assertUpdateUserSucceeds( - simong.restContext, - simong.user.id, - { email }, - () => { - // A tenant admin can delete the pending email verification token for users of their tenant - PrincipalsTestUtil.assertDeleteEmailTokenSucceeds( - camAdminRestContext, - simong.user.id, - emailForToken => { - return callback(); - } - ); - } - ); - } - ); + emailForToken => { + return callback(); } ); - } - ); - } - ); - } - ); + }); + }); + }); + }); + }); + }); }); }); }); diff --git a/packages/oae-principals/tests/test-export-data.js b/packages/oae-principals/tests/test-export-data.js index 86fb5cddeb..132da2100f 100644 --- a/packages/oae-principals/tests/test-export-data.js +++ b/packages/oae-principals/tests/test-export-data.js @@ -13,23 +13,18 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const util = require('util'); -const _ = require('underscore'); -const async = require('async'); -const dateFormat = require('dateformat'); -const jszip = require('jszip'); - -const ContentsTestUtil = require('oae-content/lib/test/util'); -const Etherpad = require('oae-content/lib/internal/etherpad'); -const RestAPI = require('oae-rest'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); - -const PrincipalsAPI = require('oae-principals'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import util from 'util'; +import _ from 'underscore'; +import dateFormat from 'dateformat'; + +import * as ContentsTestUtil from 'oae-content/lib/test/util'; +import * as Etherpad from 'oae-content/lib/internal/etherpad'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as PrincipalsAPI from 'oae-principals'; describe('Export data', () => { // Rest contexts that can be used to make requests as different types of users @@ -110,6 +105,7 @@ describe('Export data', () => { }); }); }; + doEditAndPublish(); }; @@ -138,66 +134,47 @@ describe('Export data', () => { brecke.restContext.tenant = () => { return brecke.user.tenant; }; + brecke.restContext.user = () => { return brecke.user; }; // Export personal data - PrincipalsAPI.exportData( - brecke.restContext, - 'invalidUserId', - 'personal-data', - (err, zip) => { + PrincipalsAPI.exportData(brecke.restContext, 'invalidUserId', 'personal-data', (err, zip) => { + assert.ok(err); + assert.strictEqual(400, err.code); + PrincipalsAPI.exportData('invalidContext', brecke.user.id, 'personal-data', (err, zip) => { assert.ok(err); - assert.strictEqual(400, err.code); - PrincipalsAPI.exportData( - 'invalidContext', - brecke.user.id, - 'personal-data', - (err, zip) => { - assert.ok(err); - assert.strictEqual(401, err.code); - PrincipalsAPI.exportData( - brecke.restContext, - brecke.user.id, - 'invalidExportType', - (err, zip) => { - assert.ok(err); - assert.strictEqual(402, err.code); - PrincipalsAPI.exportData( - brecke.restContext, - brecke.user.id, - 'personal-data', - (err, zip) => { - assert.ok(!err); - - // Verify the personal data on the zip file - zip - .file('personal_data.txt') - .async('string') - .then(content => { - const lines = content.split('\n'); - const element = []; - _.each(lines, (line, i) => { - element[i] = line - .split(': ') - .reverse() - .shift(); - }); - assert.strictEqual(brecke.user.id, element[0]); - assert.strictEqual(brecke.user.displayName, element[1]); - assert.strictEqual(brecke.user.email, element[2]); + assert.strictEqual(401, err.code); + PrincipalsAPI.exportData(brecke.restContext, brecke.user.id, 'invalidExportType', (err, zip) => { + assert.ok(err); + assert.strictEqual(402, err.code); + PrincipalsAPI.exportData(brecke.restContext, brecke.user.id, 'personal-data', (err, zip) => { + assert.ok(!err); - return callback(); - }); - } - ); - } - ); - } - ); - } - ); + // Verify the personal data on the zip file + zip + .file('personal_data.txt') + .async('string') + .then(content => { + const lines = content.split('\n'); + const element = []; + _.each(lines, (line, i) => { + element[i] = line + .split(': ') + .reverse() + .shift(); + }); + assert.strictEqual(brecke.user.id, element[0]); + assert.strictEqual(brecke.user.displayName, element[1]); + assert.strictEqual(brecke.user.email, element[2]); + + return callback(); + }); + }); + }); + }); + }); }); }); @@ -212,6 +189,7 @@ describe('Export data', () => { brecke.restContext.tenant = function() { return brecke.user.tenant; }; + brecke.restContext.user = function() { return brecke.user; }; @@ -250,88 +228,83 @@ describe('Export data', () => { assert.ok(!err); // Export data using 'content' export type - PrincipalsAPI.exportData( - brecke.restContext, - brecke.user.id, - 'content', - (err, zip) => { - assert.ok(!err); - - // Verify the personal data on the zip file - zip - .file('discussion_data/' + discussion.displayName + '.txt') - .async('string') - .then(zipDiscussion => { - const lines = zipDiscussion.split('\n'); - const element = []; - - _.each(lines, (line, i) => { - element[i] = line - .split(': ') - .reverse() - .shift(); - }); + PrincipalsAPI.exportData(brecke.restContext, brecke.user.id, 'content', (err, zip) => { + assert.ok(!err); - assert.strictEqual(discussion.displayName, element[0]); - - // Verify the personal data on the zip file - zip - .file('discussion_data/' + discussion.displayName + '.txt') - .async('string') - .then(zipDiscussion => { - const lines = zipDiscussion.split('\n'); - const element = []; - - _.each(lines, (line, i) => { - element[i] = line - .split(': ') - .reverse() - .shift(); - }); + // Verify the personal data on the zip file + zip + .file('discussion_data/' + discussion.displayName + '.txt') + .async('string') + .then(zipDiscussion => { + const lines = zipDiscussion.split('\n'); + const element = []; - assert.strictEqual(secondDiscussion.displayName, element[0]); - - // Verify the personal data on the zip file - zip - .file('discussion_data/' + discussion.displayName + '(1).txt') - .async('string') - .then(zipDiscussion => { - const lines = zipDiscussion.split('\n'); - const element = []; - - _.each(lines, (line, i) => { - element[i] = line - .split(': ') - .reverse() - .shift(); - }); + _.each(lines, (line, i) => { + element[i] = line + .split(': ') + .reverse() + .shift(); + }); - assert.strictEqual(discussion.displayName, element[0]); + assert.strictEqual(discussion.displayName, element[0]); + + // Verify the personal data on the zip file + zip + .file('discussion_data/' + discussion.displayName + '.txt') + .async('string') + .then(zipDiscussion => { + const lines = zipDiscussion.split('\n'); + const element = []; + + _.each(lines, (line, i) => { + element[i] = line + .split(': ') + .reverse() + .shift(); + }); - // Verify the personal data on the zip file - zip - .file('discussion_data/' + discussion.displayName + '(2).txt') - .async('string') - .then(zipDiscussion => { - const lines = zipDiscussion.split('\n'); - const element = []; + assert.strictEqual(secondDiscussion.displayName, element[0]); + + // Verify the personal data on the zip file + zip + .file('discussion_data/' + discussion.displayName + '(1).txt') + .async('string') + .then(zipDiscussion => { + const lines = zipDiscussion.split('\n'); + const element = []; + + _.each(lines, (line, i) => { + element[i] = line + .split(': ') + .reverse() + .shift(); + }); - _.each(lines, (line, i) => { - element[i] = line - .split(': ') - .reverse() - .shift(); - }); + assert.strictEqual(discussion.displayName, element[0]); + + // Verify the personal data on the zip file + zip + .file('discussion_data/' + discussion.displayName + '(2).txt') + .async('string') + .then(zipDiscussion => { + const lines = zipDiscussion.split('\n'); + const element = []; + + _.each(lines, (line, i) => { + element[i] = line + .split(': ') + .reverse() + .shift(); + }); - assert.strictEqual(thirdDiscussion.displayName, element[0]); + assert.strictEqual(thirdDiscussion.displayName, element[0]); - return callback(); - }); - }); - }); - }); - } - ); + return callback(); + }); + }); + }); + }); + }); } ); } @@ -360,6 +333,7 @@ describe('Export data', () => { brecke.restContext.tenant = function() { return brecke.user.tenant; }; + brecke.restContext.user = function() { return brecke.user; }; @@ -378,93 +352,77 @@ describe('Export data', () => { // Give one of the users a profile picture const cropArea = { x: 0, y: 0, width: 50, height: 50 }; - RestAPI.User.uploadPicture( - brecke.restContext, - brecke.user.id, - getPictureStream, - cropArea, - err => { + RestAPI.User.uploadPicture(brecke.restContext, brecke.user.id, getPictureStream, cropArea, err => { + assert.ok(!err); + + // Get the object + PrincipalsAPI.exportContentData(brecke.restContext, brecke.user.id, 'content', (err, data) => { assert.ok(!err); - // Get the object - PrincipalsAPI.exportContentData( - brecke.restContext, - brecke.user.id, - 'content', - (err, data) => { - assert.ok(!err); + // Export data using 'content' export type + PrincipalsAPI.exportData(brecke.restContext, brecke.user.id, 'content', (err, zip) => { + assert.ok(!err); - // Export data using 'content' export type - PrincipalsAPI.exportData( - brecke.restContext, - brecke.user.id, - 'content', - (err, zip) => { - assert.ok(!err); - - // Verify the personal data on the zip file - zip - .file('link_data/' + link.displayName + '.txt') - .async('string') - .then(zipLink => { - const lines = zipLink.split('\n'); - const element = []; - - _.each(lines, (line, i) => { - element[i] = line - .split(': ') - .reverse() - .shift(); - }); + // Verify the personal data on the zip file + zip + .file('link_data/' + link.displayName + '.txt') + .async('string') + .then(zipLink => { + const lines = zipLink.split('\n'); + const element = []; + + _.each(lines, (line, i) => { + element[i] = line + .split(': ') + .reverse() + .shift(); + }); - assert.strictEqual(link.displayName, element[0]); - assert.strictEqual(link.profilePath, element[1]); - assert.strictEqual('http://google.com', element[2]); - assert.strictEqual(link.visibility, element[3]); - assert.strictEqual(link.tenant.displayName, element[4]); - - // Verify the personal data on the zip file - zip - .file('collabdoc_data/' + collabdoc.displayName + '.txt') - .async('string') - .then(zipCollabdoc => { - const lines = zipCollabdoc.split('\n'); - const element = []; - - _.each(lines, (line, i) => { - element[i] = line - .split(': ') - .reverse() - .shift(); - }); + assert.strictEqual(link.displayName, element[0]); + assert.strictEqual(link.profilePath, element[1]); + assert.strictEqual('http://google.com', element[2]); + assert.strictEqual(link.visibility, element[3]); + assert.strictEqual(link.tenant.displayName, element[4]); - assert.strictEqual(collabdoc.displayName, element[0]); - assert.strictEqual(collabdoc.profilePath, element[1]); - assert.strictEqual(collabdoc.visibility, element[2]); - assert.strictEqual(collabdoc.tenant.displayName, element[3]); - assert.strictEqual('undefined', element[4]); + // Verify the personal data on the zip file + zip + .file('collabdoc_data/' + collabdoc.displayName + '.txt') + .async('string') + .then(zipCollabdoc => { + const lines = zipCollabdoc.split('\n'); + const element = []; - // Verify the personal data on the zip file - zip - .file('large.jpg') - .async('uint8array') - .then(zipPicture => { - assert.ok(zipPicture); + _.each(lines, (line, i) => { + element[i] = line + .split(': ') + .reverse() + .shift(); + }); - // Compare the object with the zip content - assert.strictEqual(zipCollabdoc, data.collabdocData[0].text); - assert.strictEqual(zipLink, data.linkData[0].text); + assert.strictEqual(collabdoc.displayName, element[0]); + assert.strictEqual(collabdoc.profilePath, element[1]); + assert.strictEqual(collabdoc.visibility, element[2]); + assert.strictEqual(collabdoc.tenant.displayName, element[3]); + assert.strictEqual('undefined', element[4]); - return callback(); - }); - }); - }); - } - ); - } - ); - } - ); + // Verify the personal data on the zip file + zip + .file('large.jpg') + .async('uint8array') + .then(zipPicture => { + assert.ok(zipPicture); + + // Compare the object with the zip content + assert.strictEqual(zipCollabdoc, data.collabdocData[0].text); + assert.strictEqual(zipLink, data.linkData[0].text); + + return callback(); + }); + }); + }); + }); + }); + }); } ); }); @@ -481,6 +439,7 @@ describe('Export data', () => { brecke.restContext.tenant = function() { return brecke.user.tenant; }; + brecke.restContext.user = function() { return brecke.user; }; @@ -497,54 +456,41 @@ describe('Export data', () => { assert.ok(!err); // Get the object - PrincipalsAPI.exportContentData( - brecke.restContext, - brecke.user.id, - 'content', - (err, data) => { - assert.ok(!err); - - // Export data using 'content' export type - PrincipalsAPI.exportData( - brecke.restContext, - brecke.user.id, - 'content', - (err, zip) => { - assert.ok(!err); - - // Verify the personal data on the zip file - zip - .file('discussion_data/' + discussion.displayName + '.txt') - .async('string') - .then(zipDiscussion => { - const lines = zipDiscussion.split('\n'); - const element = []; - - _.each(lines, (line, i) => { - element[i] = line - .split(': ') - .reverse() - .shift(); - }); - - assert.strictEqual(discussion.displayName, element[0]); - assert.strictEqual(discussion.description, element[1]); - assert.strictEqual( - discussion.tenant.host + discussion.profilePath, - element[2] - ); - assert.strictEqual(discussion.visibility, element[3]); - assert.strictEqual(discussion.tenant.displayName, element[4]); + PrincipalsAPI.exportContentData(brecke.restContext, brecke.user.id, 'content', (err, data) => { + assert.ok(!err); - // Compare the object with the zip content - assert.strictEqual(zipDiscussion, data.discussionData[0].text); + // Export data using 'content' export type + PrincipalsAPI.exportData(brecke.restContext, brecke.user.id, 'content', (err, zip) => { + assert.ok(!err); - return callback(); - }); - } - ); - } - ); + // Verify the personal data on the zip file + zip + .file('discussion_data/' + discussion.displayName + '.txt') + .async('string') + .then(zipDiscussion => { + const lines = zipDiscussion.split('\n'); + const element = []; + + _.each(lines, (line, i) => { + element[i] = line + .split(': ') + .reverse() + .shift(); + }); + + assert.strictEqual(discussion.displayName, element[0]); + assert.strictEqual(discussion.description, element[1]); + assert.strictEqual(discussion.tenant.host + discussion.profilePath, element[2]); + assert.strictEqual(discussion.visibility, element[3]); + assert.strictEqual(discussion.tenant.displayName, element[4]); + + // Compare the object with the zip content + assert.strictEqual(zipDiscussion, data.discussionData[0].text); + + return callback(); + }); + }); + }); } ); }); @@ -561,6 +507,7 @@ describe('Export data', () => { brecke.restContext.tenant = function() { return brecke.user.tenant; }; + brecke.restContext.user = function() { return brecke.user; }; @@ -579,51 +526,41 @@ describe('Export data', () => { assert.ok(!err); // Get the object - PrincipalsAPI.exportContentData( - brecke.restContext, - brecke.user.id, - 'content', - (err, data) => { - assert.ok(!err); - - // Export data using 'content' export type - PrincipalsAPI.exportData( - brecke.restContext, - brecke.user.id, - 'content', - (err, zip) => { - assert.ok(!err); - - // Verify the personal data on the zip file - zip - .file('meeting_data/' + meeting.displayName + '.txt') - .async('string') - .then(zipMeeting => { - const lines = zipMeeting.split('\n'); - const element = []; - - _.each(lines, (line, i) => { - element[i] = line - .split(': ') - .reverse() - .shift(); - }); - - assert.strictEqual(meeting.displayName, element[0]); - assert.strictEqual(meeting.description, element[1]); - assert.strictEqual(meeting.tenant.host + meeting.profilePath, element[2]); - assert.strictEqual(meeting.visibility, element[3]); - assert.strictEqual(meeting.tenant.displayName, element[4]); + PrincipalsAPI.exportContentData(brecke.restContext, brecke.user.id, 'content', (err, data) => { + assert.ok(!err); - // Compare the object with the zip content - assert.strictEqual(zipMeeting, data.meetingData[0].text); + // Export data using 'content' export type + PrincipalsAPI.exportData(brecke.restContext, brecke.user.id, 'content', (err, zip) => { + assert.ok(!err); - return callback(); - }); - } - ); - } - ); + // Verify the personal data on the zip file + zip + .file('meeting_data/' + meeting.displayName + '.txt') + .async('string') + .then(zipMeeting => { + const lines = zipMeeting.split('\n'); + const element = []; + + _.each(lines, (line, i) => { + element[i] = line + .split(': ') + .reverse() + .shift(); + }); + + assert.strictEqual(meeting.displayName, element[0]); + assert.strictEqual(meeting.description, element[1]); + assert.strictEqual(meeting.tenant.host + meeting.profilePath, element[2]); + assert.strictEqual(meeting.visibility, element[3]); + assert.strictEqual(meeting.tenant.displayName, element[4]); + + // Compare the object with the zip content + assert.strictEqual(zipMeeting, data.meetingData[0].text); + + return callback(); + }); + }); + }); } ); }); @@ -640,6 +577,7 @@ describe('Export data', () => { simon.restContext.tenant = function() { return simon.user.tenant; }; + simon.restContext.user = function() { return simon.user; }; @@ -647,6 +585,7 @@ describe('Export data', () => { brecke.restContext.tenant = function() { return brecke.user.tenant; }; + brecke.restContext.user = function() { return brecke.user; }; @@ -690,6 +629,7 @@ describe('Export data', () => { simon.restContext.tenant = function() { return simon.user.tenant; }; + simon.restContext.user = function() { return simon.user; }; @@ -697,6 +637,7 @@ describe('Export data', () => { brecke.restContext.tenant = function() { return brecke.user.tenant; }; + brecke.restContext.user = function() { return brecke.user; }; @@ -715,54 +656,39 @@ describe('Export data', () => { assert.ok(!err); // Export personal data and verify we don't get the shared content - PrincipalsAPI.exportData( - brecke.restContext, - brecke.user.id, - 'personal-data', - (err, zip) => { - assert.ok(!err); - assert.ok(!zip.files['meeting_data/breckeMeeting.txt']); + PrincipalsAPI.exportData(brecke.restContext, brecke.user.id, 'personal-data', (err, zip) => { + assert.ok(!err); + assert.ok(!zip.files['meeting_data/breckeMeeting.txt']); - // Create one new meeting - RestAPI.MeetingsJitsi.createMeeting( - simon.restContext, - 'simonMeeting', - 'description', - false, - false, - 'public', - [], - [brecke.user.id], - (err, simonMeeting) => { + // Create one new meeting + RestAPI.MeetingsJitsi.createMeeting( + simon.restContext, + 'simonMeeting', + 'description', + false, + false, + 'public', + [], + [brecke.user.id], + (err, simonMeeting) => { + assert.ok(!err); + + // Export personal data and verify we don't get the shared content + PrincipalsAPI.exportData(brecke.restContext, brecke.user.id, 'personal-data', (err, zip) => { assert.ok(!err); + assert.ok(!zip.files['meeting_data/simonMeeting.txt']); - // Export personal data and verify we don't get the shared content - PrincipalsAPI.exportData( - brecke.restContext, - brecke.user.id, - 'personal-data', - (err, zip) => { - assert.ok(!err); - assert.ok(!zip.files['meeting_data/simonMeeting.txt']); - - // Export content data and verify we don't get the shared content - PrincipalsAPI.exportData( - brecke.restContext, - brecke.user.id, - 'content', - (err, zip) => { - assert.ok(!err); - assert.ok(!zip.files['meeting_data/SimonMeeting.txt']); - - return callback(); - } - ); - } - ); - } - ); - } - ); + // Export content data and verify we don't get the shared content + PrincipalsAPI.exportData(brecke.restContext, brecke.user.id, 'content', (err, zip) => { + assert.ok(!err); + assert.ok(!zip.files['meeting_data/SimonMeeting.txt']); + + return callback(); + }); + }); + } + ); + }); } ); }); @@ -777,6 +703,7 @@ describe('Export data', () => { brecke.restContext.tenant = function() { return brecke.user.tenant; }; + brecke.restContext.user = function() { return brecke.user; }; @@ -829,6 +756,7 @@ describe('Export data', () => { brecke.restContext.tenant = function() { return brecke.user.tenant; }; + brecke.restContext.user = function() { return brecke.user; }; @@ -836,85 +764,72 @@ describe('Export data', () => { simon.restContext.tenant = function() { return simon.user.tenant; }; + simon.restContext.user = function() { return simon.user; }; // Create one comment - RestAPI.Content.createComment( - brecke.restContext, - collabdoc.id, - 'This is a comment', - null, - (err, comment) => { - assert.ok(!err); + RestAPI.Content.createComment(brecke.restContext, collabdoc.id, 'This is a comment', null, (err, comment) => { + assert.ok(!err); - // Create one more - RestAPI.Content.createComment( - simon.restContext, - collabdoc.id, - 'Another comment', - null, - (err, anotherComment) => { - assert.ok(!err); + // Create one more + RestAPI.Content.createComment( + simon.restContext, + collabdoc.id, + 'Another comment', + null, + (err, anotherComment) => { + assert.ok(!err); - // Export the 'content' data - PrincipalsAPI.exportData( - brecke.restContext, - brecke.user.id, - 'content', - (err, zip) => { - assert.ok(!err); + // Export the 'content' data + PrincipalsAPI.exportData(brecke.restContext, brecke.user.id, 'content', (err, zip) => { + assert.ok(!err); - // Verify the collabdoc data on the zip file - zip - .file('collabdoc_data/' + collabdoc.displayName + '.txt') - .async('string') - .then(zipCollabdoc => { - const lines = zipCollabdoc.split('\n'); - const element = []; + // Verify the collabdoc data on the zip file + zip + .file('collabdoc_data/' + collabdoc.displayName + '.txt') + .async('string') + .then(zipCollabdoc => { + const lines = zipCollabdoc.split('\n'); + const element = []; - _.each(lines, (line, i) => { - element[i] = line.split(': '); - }); + _.each(lines, (line, i) => { + element[i] = line.split(': '); + }); - // Get the creation date - const messageCreatedComment = dateFormat( - new Date(parseInt(comment.created)), // eslint-disable-line radix - 'dd-mm-yyyy, h:MM:ss TT' - ); - const messageCreatedAnotherComment = dateFormat( - new Date(parseInt(comment.created)), // eslint-disable-line radix - 'dd-mm-yyyy, h:MM:ss TT' - ); - - // Get the message level - const levelComment = element[8][1].split(' '); - const levelAnotherComment = element[7][1].split(' '); - - // Verify if the comment and the author of the comment are the same - assert.strictEqual(element[8][3], comment.body); - assert.ok(element[8][2].includes(comment.createdBy.publicAlias)); - assert.strictEqual(levelComment[1], comment.level.toString()); - assert.ok(element[8][0].includes(messageCreatedComment)); - - // Verify if the comment and the author of the comment are the same - assert.strictEqual(element[7][3], anotherComment.body); - assert.ok(element[7][2].includes(anotherComment.createdBy.publicAlias)); - assert.strictEqual( - levelAnotherComment[1], - anotherComment.level.toString() - ); - assert.ok(element[7][0].includes(messageCreatedAnotherComment)); - - return callback(); - }); - } - ); - } - ); - } - ); + // Get the creation date + const messageCreatedComment = dateFormat( + new Date(parseInt(comment.created)), // eslint-disable-line radix + 'dd-mm-yyyy, h:MM:ss TT' + ); + const messageCreatedAnotherComment = dateFormat( + new Date(parseInt(comment.created)), // eslint-disable-line radix + 'dd-mm-yyyy, h:MM:ss TT' + ); + + // Get the message level + const levelComment = element[8][1].split(' '); + const levelAnotherComment = element[7][1].split(' '); + + // Verify if the comment and the author of the comment are the same + assert.strictEqual(element[8][3], comment.body); + assert.ok(element[8][2].includes(comment.createdBy.publicAlias)); + assert.strictEqual(levelComment[1], comment.level.toString()); + assert.ok(element[8][0].includes(messageCreatedComment)); + + // Verify if the comment and the author of the comment are the same + assert.strictEqual(element[7][3], anotherComment.body); + assert.ok(element[7][2].includes(anotherComment.createdBy.publicAlias)); + assert.strictEqual(levelAnotherComment[1], anotherComment.level.toString()); + assert.ok(element[7][0].includes(messageCreatedAnotherComment)); + + return callback(); + }); + }); + } + ); + }); }); }); }); diff --git a/packages/oae-principals/tests/test-groups.js b/packages/oae-principals/tests/test-groups.js index 81d1ced193..3fe4ff58a6 100644 --- a/packages/oae-principals/tests/test-groups.js +++ b/packages/oae-principals/tests/test-groups.js @@ -13,25 +13,25 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const util = require('util'); -const _ = require('underscore'); - -const AuthzAPI = require('oae-authz'); -const AuthzUtil = require('oae-authz/lib/util'); -const Cassandra = require('oae-util/lib/cassandra'); -const ConfigTestUtil = require('oae-config/lib/test/util'); -const FoldersTestUtil = require('oae-folders/lib/test/util'); -const LibraryTestUtil = require('oae-library/lib/test/util'); -const RestAPI = require('oae-rest'); -const TenantsTestUtil = require('oae-tenants/lib/test/util'); -const TestsUtil = require('oae-tests'); - -const PrincipalsAPI = require('oae-principals'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const { PrincipalsConstants } = require('oae-principals/lib/constants'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); +import assert from 'assert'; +import fs from 'fs'; +import util from 'util'; +import _ from 'underscore'; + +import * as AuthzAPI from 'oae-authz'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as FoldersTestUtil from 'oae-folders/lib/test/util'; +import * as LibraryTestUtil from 'oae-library/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; +import * as TestsUtil from 'oae-tests'; +import * as PrincipalsAPI from 'oae-principals'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; + +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { PrincipalsConstants } from 'oae-principals/lib/constants'; describe('Groups', () => { // Rest context that can be used to perform requests as different types of users @@ -2503,15 +2503,15 @@ describe('Groups', () => { assert.ok(!err); /* - * Create a group tree which looks like: - * top - * / \ - * simonParentParent nicoParentParent - * / \ - * simonParent nicoParent - * / \ - * simon nico - */ + * Create a group tree which looks like: + * top + * / \ + * simonParentParent nicoParentParent + * / \ + * simonParent nicoParent + * / \ + * simon nico + */ TestsUtil.generateTestGroups( camAdminRestContext, 5, @@ -2855,6 +2855,7 @@ describe('Groups', () => { createdPrincipals[expectedMembers[i]].id ); } + return callback(); } ); @@ -2890,6 +2891,7 @@ describe('Groups', () => { createdPrincipals[expectedGroups[i]].id ); } + return callback(); } ); @@ -2984,6 +2986,7 @@ describe('Groups', () => { assert.ok(err); assert.strictEqual(err.code, 401); } + return callback(); }); }); diff --git a/packages/oae-principals/tests/test-members-library.js b/packages/oae-principals/tests/test-members-library.js index 24647aecc2..8045929d10 100644 --- a/packages/oae-principals/tests/test-members-library.js +++ b/packages/oae-principals/tests/test-members-library.js @@ -13,19 +13,12 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); - -const AuthzUtil = require('oae-authz/lib/util'); -const Cassandra = require('oae-util/lib/cassandra'); -const LibraryAPI = require('oae-library'); -const RestAPI = require('oae-rest'); -const { SearchConstants } = require('oae-search/lib/constants'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); - -const { PrincipalsConstants } = require('oae-principals/lib/constants'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); +import assert from 'assert'; +import _ from 'underscore'; + +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; describe('Members Library', () => { // REST contexts we can use to do REST requests diff --git a/packages/oae-principals/tests/test-members-search.js b/packages/oae-principals/tests/test-members-search.js index 5aad9594eb..567b1dd820 100644 --- a/packages/oae-principals/tests/test-members-search.js +++ b/packages/oae-principals/tests/test-members-search.js @@ -13,23 +13,23 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const util = require('util'); -const _ = require('underscore'); +import assert from 'assert'; +import util from 'util'; +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const RestAPI = require('oae-rest'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as RestAPI from 'oae-rest'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests'; describe('Members Library Search', () => { /*! - * Get the document with the specified id from the search results. - * - * @param {SearchResult} results The search results object - * @param {String} docId The id of the document to search - * @return {Object} The search document. `null` if it didn't exist - */ + * Get the document with the specified id from the search results. + * + * @param {SearchResult} results The search results object + * @param {String} docId The id of the document to search + * @return {Object} The search document. `null` if it didn't exist + */ const _getDocById = function(results, docId) { for (let i = 0; i < results.results.length; i++) { const doc = results.results[i]; @@ -37,6 +37,7 @@ describe('Members Library Search', () => { return doc; } } + return null; }; @@ -63,197 +64,177 @@ describe('Members Library Search', () => { gtAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.gt.host); /*! - * Creates the following variable setup for testing members search: - * - * Users: - * privateUserMember: A user with visibility 'private'. Is a member of all the target groups. - * loggedinUserMember: A user with visibility 'loggedin'. Is a member of all the target groups. - * publicUserMember: A user with visibility 'public'. Is a member of all the target groups. - * - * Target Groups: - * targetPublicGroup: A group with visibility 'public' that will be a target of members search. - * targetLoggedinGroup: A group with visibility 'loggedin' that will be a target of members search. - * targetPrivateGroup: A group with visibility 'private' that will be a target of members search. - * - * Member Groups: - * publicGroupMember: A group with visibility 'public'. Is a member of all the target groups. - * privateGroupMember: A group with visibility 'private'. Is a member of all the target groups. - */ - TestsUtil.generateTestUsers( - camAdminRestContext, - 4, - (err, users, doer, publicUser, loggedinUser, privateUser) => { - assert.ok(!err); + * Creates the following variable setup for testing members search: + * + * Users: + * privateUserMember: A user with visibility 'private'. Is a member of all the target groups. + * loggedinUserMember: A user with visibility 'loggedin'. Is a member of all the target groups. + * publicUserMember: A user with visibility 'public'. Is a member of all the target groups. + * + * Target Groups: + * targetPublicGroup: A group with visibility 'public' that will be a target of members search. + * targetLoggedinGroup: A group with visibility 'loggedin' that will be a target of members search. + * targetPrivateGroup: A group with visibility 'private' that will be a target of members search. + * + * Member Groups: + * publicGroupMember: A group with visibility 'public'. Is a member of all the target groups. + * privateGroupMember: A group with visibility 'private'. Is a member of all the target groups. + */ + TestsUtil.generateTestUsers(camAdminRestContext, 4, (err, users, doer, publicUser, loggedinUser, privateUser) => { + assert.ok(!err); - doerRestContext = doer.restContext; - publicUserMember = publicUser.user; + doerRestContext = doer.restContext; + publicUserMember = publicUser.user; - const loggedinOpts = { - visibility: 'loggedin', - publicAlias: 'LoggedinHidden' - }; - RestAPI.User.updateUser( - loggedinUser.restContext, - loggedinUser.user.id, - loggedinOpts, - (err, loggedinUser) => { - assert.ok(!err); - loggedinUserMember = loggedinUser; - - const privateOpts = { - visibility: 'private', - publicAlias: 'PrivateHidden' - }; - RestAPI.User.updateUser( - privateUser.restContext, - privateUser.user.id, - privateOpts, - (err, privateUser) => { - assert.ok(!err); - privateUserMember = privateUser; + const loggedinOpts = { + visibility: 'loggedin', + publicAlias: 'LoggedinHidden' + }; + RestAPI.User.updateUser(loggedinUser.restContext, loggedinUser.user.id, loggedinOpts, (err, loggedinUser) => { + assert.ok(!err); + loggedinUserMember = loggedinUser; - RestAPI.Group.createGroup( - doerRestContext, - TestsUtil.generateTestUserId('targetPublicGroup'), - TestsUtil.generateTestUserId('targetPublicGroup'), - 'public', - 'no', - [], - [], - (err, _targetPublicGroup) => { - assert.ok(!err); - targetPublicGroup = _targetPublicGroup; - - RestAPI.Group.createGroup( - doerRestContext, - TestsUtil.generateTestUserId('targetLoggedinGroup'), - TestsUtil.generateTestUserId('targetLoggedinGroup'), - 'loggedin', - 'no', - [], - [], - (err, _targetLoggedinGroup) => { - assert.ok(!err); - targetLoggedinGroup = _targetLoggedinGroup; - - RestAPI.Group.createGroup( - doerRestContext, - TestsUtil.generateTestUserId('targetPrivateGroup'), - TestsUtil.generateTestUserId('targetPrivateGroup'), - 'private', - 'no', - [], - [], - (err, _targetPrivateGroup) => { - assert.ok(!err); - targetPrivateGroup = _targetPrivateGroup; - - RestAPI.Group.createGroup( - doerRestContext, - TestsUtil.generateTestUserId('publicGroupMemberAlias'), - TestsUtil.generateTestUserId('publicGroupMemberAlias'), - 'public', - 'no', - [], - [], - (err, _publicGroupMember) => { - assert.ok(!err); - publicGroupMember = _publicGroupMember; - - RestAPI.Group.createGroup( - doerRestContext, - TestsUtil.generateTestUserId('privateGroupMemberAlias'), - TestsUtil.generateTestUserId('privateGroupMemberAlias'), - 'private', - 'no', - [], - [], - (err, _privateGroupMember) => { - assert.ok(!err); - privateGroupMember = _privateGroupMember; - - const memberships = {}; - memberships[publicGroupMember.id] = 'member'; - memberships[privateGroupMember.id] = 'member'; - memberships[publicUserMember.id] = 'member'; - memberships[loggedinUserMember.id] = 'member'; - memberships[privateUserMember.id] = 'member'; - - // Set the members of the groups with the cam admin since they are the only ones with access to make the private - // user a member - RestAPI.Group.setGroupMembers( - camAdminRestContext, - targetPublicGroup.id, - memberships, - err => { - assert.ok(!err); - - RestAPI.Group.setGroupMembers( - camAdminRestContext, - targetLoggedinGroup.id, - memberships, - err => { - assert.ok(!err); - - RestAPI.Group.setGroupMembers( - camAdminRestContext, - targetPrivateGroup.id, - memberships, - err => { - assert.ok(!err); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + const privateOpts = { + visibility: 'private', + publicAlias: 'PrivateHidden' + }; + RestAPI.User.updateUser(privateUser.restContext, privateUser.user.id, privateOpts, (err, privateUser) => { + assert.ok(!err); + privateUserMember = privateUser; + + RestAPI.Group.createGroup( + doerRestContext, + TestsUtil.generateTestUserId('targetPublicGroup'), + TestsUtil.generateTestUserId('targetPublicGroup'), + 'public', + 'no', + [], + [], + (err, _targetPublicGroup) => { + assert.ok(!err); + targetPublicGroup = _targetPublicGroup; + + RestAPI.Group.createGroup( + doerRestContext, + TestsUtil.generateTestUserId('targetLoggedinGroup'), + TestsUtil.generateTestUserId('targetLoggedinGroup'), + 'loggedin', + 'no', + [], + [], + (err, _targetLoggedinGroup) => { + assert.ok(!err); + targetLoggedinGroup = _targetLoggedinGroup; + + RestAPI.Group.createGroup( + doerRestContext, + TestsUtil.generateTestUserId('targetPrivateGroup'), + TestsUtil.generateTestUserId('targetPrivateGroup'), + 'private', + 'no', + [], + [], + (err, _targetPrivateGroup) => { + assert.ok(!err); + targetPrivateGroup = _targetPrivateGroup; + + RestAPI.Group.createGroup( + doerRestContext, + TestsUtil.generateTestUserId('publicGroupMemberAlias'), + TestsUtil.generateTestUserId('publicGroupMemberAlias'), + 'public', + 'no', + [], + [], + (err, _publicGroupMember) => { + assert.ok(!err); + publicGroupMember = _publicGroupMember; + + RestAPI.Group.createGroup( + doerRestContext, + TestsUtil.generateTestUserId('privateGroupMemberAlias'), + TestsUtil.generateTestUserId('privateGroupMemberAlias'), + 'private', + 'no', + [], + [], + (err, _privateGroupMember) => { + assert.ok(!err); + privateGroupMember = _privateGroupMember; + + const memberships = {}; + memberships[publicGroupMember.id] = 'member'; + memberships[privateGroupMember.id] = 'member'; + memberships[publicUserMember.id] = 'member'; + memberships[loggedinUserMember.id] = 'member'; + memberships[privateUserMember.id] = 'member'; + + // Set the members of the groups with the cam admin since they are the only ones with access to make the private + // user a member + RestAPI.Group.setGroupMembers( + camAdminRestContext, + targetPublicGroup.id, + memberships, + err => { + assert.ok(!err); + + RestAPI.Group.setGroupMembers( + camAdminRestContext, + targetLoggedinGroup.id, + memberships, + err => { + assert.ok(!err); + + RestAPI.Group.setGroupMembers( + camAdminRestContext, + targetPrivateGroup.id, + memberships, + err => { + assert.ok(!err); + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); + }); + }); }); /** * Test that verifies a user cannot search members of something that is not a valid group id or non-existing group */ it('verify cannot search for invalid or non-existent group', callback => { - SearchTestsUtil.searchAll( - anonymousRestContext, - 'members-library', - ['not-a-group-id'], - null, - (err, results) => { - assert.ok(err); - assert.strictEqual(err.code, 400); - assert.ok(!results); - - SearchTestsUtil.searchAll( - anonymousRestContext, - 'members-library', - [util.format('g:%s:nonexistent-group-id', global.oaeTests.tenants.cam.alias)], - null, - (err, results) => { - assert.ok(err); - assert.strictEqual(err.code, 404); - assert.ok(!results); - return callback(); - } - ); - } - ); + SearchTestsUtil.searchAll(anonymousRestContext, 'members-library', ['not-a-group-id'], null, (err, results) => { + assert.ok(err); + assert.strictEqual(err.code, 400); + assert.ok(!results); + + SearchTestsUtil.searchAll( + anonymousRestContext, + 'members-library', + [util.format('g:%s:nonexistent-group-id', global.oaeTests.tenants.cam.alias)], + null, + (err, results) => { + assert.ok(err); + assert.strictEqual(err.code, 404); + assert.ok(!results); + return callback(); + } + ); + }); }); /** @@ -383,14 +364,8 @@ describe('Members Library Search', () => { assert.ok(publicGroupResult); // Verify user visibility. Private should have their publicAlias swapped into the title - assert.strictEqual( - publicUserResult.displayName, - publicUserMember.displayName - ); - assert.strictEqual( - loggedinUserResult.displayName, - loggedinUserMember.displayName - ); + assert.strictEqual(publicUserResult.displayName, publicUserMember.displayName); + assert.strictEqual(loggedinUserResult.displayName, loggedinUserMember.displayName); // There should be no extra right now because we haven't added extension properties assert.ok(!loggedinUserResult.extra); @@ -399,10 +374,7 @@ describe('Members Library Search', () => { assert.strictEqual(loggedinUserResult.q_high, undefined); assert.strictEqual(loggedinUserResult.q_low, undefined); assert.strictEqual(loggedinUserResult.sort, undefined); - assert.strictEqual( - publicGroupResult.displayName, - publicGroupMember.displayName - ); + assert.strictEqual(publicGroupResult.displayName, publicGroupMember.displayName); // Verify that the correct resourceTypes are set assert.strictEqual(publicUserResult.resourceType, 'user'); @@ -453,14 +425,8 @@ describe('Members Library Search', () => { assert.ok(publicGroupResult); // Verify user visibility. Private should have their publicAlias swapped into the title - assert.strictEqual( - publicUserResult.displayName, - publicUserMember.displayName - ); - assert.strictEqual( - loggedinUserResult.displayName, - loggedinUserMember.displayName - ); + assert.strictEqual(publicUserResult.displayName, publicUserMember.displayName); + assert.strictEqual(loggedinUserResult.displayName, loggedinUserMember.displayName); // There should be no extra right now because we haven't added extension properties assert.ok(!loggedinUserResult.extra); @@ -468,18 +434,9 @@ describe('Members Library Search', () => { assert.strictEqual(loggedinUserResult.q_high, undefined); assert.strictEqual(loggedinUserResult.q_low, undefined); assert.strictEqual(loggedinUserResult.sort, undefined); - assert.strictEqual( - privateUserResult.displayName, - privateUserMember.publicAlias - ); - assert.strictEqual( - publicGroupResult.displayName, - publicGroupMember.displayName - ); - assert.strictEqual( - privateGroupResult.displayName, - privateGroupMember.displayName - ); + assert.strictEqual(privateUserResult.displayName, privateUserMember.publicAlias); + assert.strictEqual(publicGroupResult.displayName, publicGroupMember.displayName); + assert.strictEqual(privateGroupResult.displayName, privateGroupMember.displayName); // Verify that the correct resourceTypes are set assert.strictEqual(publicUserResult.resourceType, 'user'); @@ -591,14 +548,8 @@ describe('Members Library Search', () => { assert.ok(publicGroupResult); // Verify user visibility. Private should have their publicAlias swapped into the title - assert.strictEqual( - publicUserResult.displayName, - publicUserMember.displayName - ); - assert.strictEqual( - loggedinUserResult.displayName, - loggedinUserMember.displayName - ); + assert.strictEqual(publicUserResult.displayName, publicUserMember.displayName); + assert.strictEqual(loggedinUserResult.displayName, loggedinUserMember.displayName); // There should be no extra right now because we haven't added extension properties assert.ok(!loggedinUserResult.extra); @@ -607,10 +558,7 @@ describe('Members Library Search', () => { assert.strictEqual(loggedinUserResult.q_high, undefined); assert.strictEqual(loggedinUserResult.q_low, undefined); assert.strictEqual(loggedinUserResult.sort, undefined); - assert.strictEqual( - publicGroupResult.displayName, - publicGroupMember.displayName - ); + assert.strictEqual(publicGroupResult.displayName, publicGroupMember.displayName); // Verify that the correct resourceTypes are set assert.strictEqual(publicUserResult.resourceType, 'user'); @@ -662,14 +610,8 @@ describe('Members Library Search', () => { assert.ok(publicGroupResult); // Verify user visibility. Private should have their publicAlias swapped into the title - assert.strictEqual( - publicUserResult.displayName, - publicUserMember.displayName - ); - assert.strictEqual( - loggedinUserResult.displayName, - loggedinUserMember.displayName - ); + assert.strictEqual(publicUserResult.displayName, publicUserMember.displayName); + assert.strictEqual(loggedinUserResult.displayName, loggedinUserMember.displayName); // There should be no extra right now because we haven't added extension properties assert.ok(!loggedinUserResult.extra); @@ -678,18 +620,9 @@ describe('Members Library Search', () => { assert.strictEqual(loggedinUserResult.q_high, undefined); assert.strictEqual(loggedinUserResult.q_low, undefined); assert.strictEqual(loggedinUserResult.sort, undefined); - assert.strictEqual( - privateUserResult.displayName, - privateUserMember.publicAlias - ); - assert.strictEqual( - publicGroupResult.displayName, - publicGroupMember.displayName - ); - assert.strictEqual( - privateGroupResult.displayName, - privateGroupMember.displayName - ); + assert.strictEqual(privateUserResult.displayName, privateUserMember.publicAlias); + assert.strictEqual(publicGroupResult.displayName, publicGroupMember.displayName); + assert.strictEqual(privateGroupResult.displayName, privateGroupMember.displayName); // Verify that the correct resourceTypes are set assert.strictEqual(publicUserResult.resourceType, 'user'); @@ -812,14 +745,8 @@ describe('Members Library Search', () => { assert.ok(publicGroupResult); // Verify user visibility. Private should have their publicAlias swapped into the title - assert.strictEqual( - publicUserResult.displayName, - publicUserMember.displayName - ); - assert.strictEqual( - loggedinUserResult.displayName, - loggedinUserMember.displayName - ); + assert.strictEqual(publicUserResult.displayName, publicUserMember.displayName); + assert.strictEqual(loggedinUserResult.displayName, loggedinUserMember.displayName); // There should be no extra right now because we haven't added extension properties assert.ok(!loggedinUserResult.extra); @@ -828,18 +755,9 @@ describe('Members Library Search', () => { assert.strictEqual(loggedinUserResult.q_high, undefined); assert.strictEqual(loggedinUserResult.q_low, undefined); assert.strictEqual(loggedinUserResult.sort, undefined); - assert.strictEqual( - privateUserResult.displayName, - privateUserMember.publicAlias - ); - assert.strictEqual( - publicGroupResult.displayName, - publicGroupMember.displayName - ); - assert.strictEqual( - privateGroupResult.displayName, - privateGroupMember.displayName - ); + assert.strictEqual(privateUserResult.displayName, privateUserMember.publicAlias); + assert.strictEqual(publicGroupResult.displayName, publicGroupMember.displayName); + assert.strictEqual(privateGroupResult.displayName, privateGroupMember.displayName); // Verify that the correct resourceTypes are set assert.strictEqual(publicUserResult.resourceType, 'user'); diff --git a/packages/oae-principals/tests/test-memberships-library.js b/packages/oae-principals/tests/test-memberships-library.js index 4b44d2e174..867abd916a 100644 --- a/packages/oae-principals/tests/test-memberships-library.js +++ b/packages/oae-principals/tests/test-memberships-library.js @@ -13,19 +13,18 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const Cassandra = require('oae-util/lib/cassandra'); -const LibraryAPI = require('oae-library'); -const RestAPI = require('oae-rest'); -const { SearchConstants } = require('oae-search/lib/constants'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as LibraryAPI from 'oae-library'; +import * as RestAPI from 'oae-rest'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; -const { PrincipalsConstants } = require('oae-principals/lib/constants'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); +import { PrincipalsConstants } from 'oae-principals/lib/constants'; describe('Memberships Library', () => { // REST contexts we can use to do REST requests @@ -582,12 +581,12 @@ describe('Memberships Library', () => { describe('Search', () => { /*! - * Get the document with the specified id from the search results. - * - * @param {SearchResult} results The search results object - * @param {String} docId The id of the document to search - * @return {Object} The search document. `null` if it didn't exist - */ + * Get the document with the specified id from the search results. + * + * @param {SearchResult} results The search results object + * @param {String} docId The id of the document to search + * @return {Object} The search document. `null` if it didn't exist + */ const _getDocById = function(results, docId) { for (let i = 0; i < results.results.length; i++) { const doc = results.results[i]; @@ -595,6 +594,7 @@ describe('Memberships Library', () => { return doc; } } + return null; }; diff --git a/packages/oae-principals/tests/test-migrations.js b/packages/oae-principals/tests/test-migrations.js index 95478febfe..0a1551c463 100644 --- a/packages/oae-principals/tests/test-migrations.js +++ b/packages/oae-principals/tests/test-migrations.js @@ -13,19 +13,18 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); -const Chance = require('chance'); +import assert from 'assert'; +import _ from 'underscore'; +import Chance from 'chance'; -const AuthzTestUtil = require('oae-authz/lib/test/util'); -const Cassandra = require('oae-util/lib/cassandra'); -const ContentTestUtil = require('oae-content/lib/test/util'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const Redis = require('oae-util/lib/redis'); -const RestAPI = require('oae-rest'); -const SearchTestUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); -const LowerCaseEmailsMigrator = require('../../../etc/migration/12.3-to-12.4/lib/lower-case-emails'); +import * as AuthzTestUtil from 'oae-authz/lib/test/util'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as ContentTestUtil from 'oae-content/lib/test/util'; +import * as Redis from 'oae-util/lib/redis'; +import * as RestAPI from 'oae-rest'; +import * as SearchTestUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests'; +import * as LowerCaseEmailsMigrator from '../../../etc/migration/12.3-to-12.4/lib/lower-case-emails'; const chance = new Chance(); @@ -40,30 +39,31 @@ describe('Principals Migration', () => { }); /*! - * Randomly mix the case of the given string - * - * @param {String} toMix The string whose case to mix - * @return {String} The string with its case mixed - */ + * Randomly mix the case of the given string + * + * @param {String} toMix The string whose case to mix + * @return {String} The string with its case mixed + */ const _mixCase = function(toMix) { return _.map(toMix, c => { if (chance.bool()) { return c.toUpperCase(); } + return c.toLowerCase(); }).join(''); }; /*! - * Search for the email using email search, ensuring either their - * presence or absense, depending on the `shouldContain` option - * - * @param {String[]} emails The email addresses for which to search - * @param {Object} opts Execution options - * @param {Boolean} opts.shouldContain Whether or not the search results should contain an associated user - * @param {Function} callback Invoked when assertions are complete - * @throws {AssertionError} Thrown if the assertions fail - */ + * Search for the email using email search, ensuring either their + * presence or absense, depending on the `shouldContain` option + * + * @param {String[]} emails The email addresses for which to search + * @param {Object} opts Execution options + * @param {Boolean} opts.shouldContain Whether or not the search results should contain an associated user + * @param {Function} callback Invoked when assertions are complete + * @throws {AssertionError} Thrown if the assertions fail + */ const _assertAllEmailsSearch = function(emails, opts, callback) { emails = emails.slice(); if (_.isEmpty(emails)) { @@ -71,35 +71,30 @@ describe('Principals Migration', () => { } const email = emails.shift(); - SearchTestUtil.assertSearchSucceeds( - camAdminRestContext, - 'email', - null, - { q: email }, - result => { - if (opts.shouldContain) { - assert.strictEqual(result.results.length, 1); - assert.strictEqual(result.results[0].email, email.toLowerCase()); - } else { - assert.strictEqual(result.results.length, 0); - } - return _assertAllEmailsSearch(emails, opts, callback); + SearchTestUtil.assertSearchSucceeds(camAdminRestContext, 'email', null, { q: email }, result => { + if (opts.shouldContain) { + assert.strictEqual(result.results.length, 1); + assert.strictEqual(result.results[0].email, email.toLowerCase()); + } else { + assert.strictEqual(result.results.length, 0); } - ); + + return _assertAllEmailsSearch(emails, opts, callback); + }); }; /*! - * Add the given emails to a content item, ensuring they are either added as - * invitations or members, depending on if we expect the emails to be - * found in the PrincipalsByEmail index. - * - * @param {RestContext} restContext The REST context with which to share - * @param {String[]} emails The email addresses with which to share - * @param {Object} opts Execution options - * @param {Boolean} opts.shouldInvite Whether we should expect the share to result in email invitations - * @param {Function} callback Standard callback function - * @throws {AssertionError} Thrown if the assertions fail - */ + * Add the given emails to a content item, ensuring they are either added as + * invitations or members, depending on if we expect the emails to be + * found in the PrincipalsByEmail index. + * + * @param {RestContext} restContext The REST context with which to share + * @param {String[]} emails The email addresses with which to share + * @param {Object} opts Execution options + * @param {Boolean} opts.shouldInvite Whether we should expect the share to result in email invitations + * @param {Function} callback Standard callback function + * @throws {AssertionError} Thrown if the assertions fail + */ const _assertAllEmailsInvite = function(restContext, emails, opts, callback) { ContentTestUtil.assertCreateLinkSucceeds( restContext, @@ -117,39 +112,31 @@ describe('Principals Migration', () => { // principal and this operation would fail RestAPI.Content.shareContent(restContext, link.id, emails, err => { assert.ok(!err); - AuthzTestUtil.assertGetInvitationsSucceeds( - restContext, - 'content', - link.id, - invitations => { - ContentTestUtil.getAllContentMembers(restContext, link.id, null, members => { - // Since invitations will be lower-cased, to compare - // the arrays we should lower case our mixed-case local - // copy - const lowerCased = _.map(emails, email => { - return email.toLowerCase(); - }); + AuthzTestUtil.assertGetInvitationsSucceeds(restContext, 'content', link.id, invitations => { + ContentTestUtil.getAllContentMembers(restContext, link.id, null, members => { + // Since invitations will be lower-cased, to compare + // the arrays we should lower case our mixed-case local + // copy + const lowerCased = _.map(emails, email => { + return email.toLowerCase(); + }); - if (opts.shouldInvite) { - // If we expected to invite them, ensure they are - // all present in the invitations list - assert.deepStrictEqual( - _.pluck(invitations.results, 'email').sort(), - lowerCased.sort() - ); - assert.strictEqual(members.length, 1); - } else { - // If we expected to add them (i.e., their emails - // were found in the system), then verify they exist - // as members - assert.strictEqual(invitations.results.length, 0); - assert.strictEqual(members.length, emails.length + 1); - } + if (opts.shouldInvite) { + // If we expected to invite them, ensure they are + // all present in the invitations list + assert.deepStrictEqual(_.pluck(invitations.results, 'email').sort(), lowerCased.sort()); + assert.strictEqual(members.length, 1); + } else { + // If we expected to add them (i.e., their emails + // were found in the system), then verify they exist + // as members + assert.strictEqual(invitations.results.length, 0); + assert.strictEqual(members.length, emails.length + 1); + } - return callback(); - }); - } - ); + return callback(); + }); + }); }); } ); @@ -246,39 +233,30 @@ describe('Principals Migration', () => { // Ensure that none of the emails can be searched for or linked to when sharing a content // item. This is the basis for the migration _assertAllEmailsSearch(emailsWithUpperCase, { shouldContain: false }, () => { - _assertAllEmailsInvite( - actor.restContext, - emailsWithUpperCase, - { shouldInvite: true }, - () => { - // Run the migration - LowerCaseEmailsMigrator.doMigration((err, stats) => { - assert.ok(!err); - assert.strictEqual(stats.nUpdated, emailsWithUpperCase.length); - assert.strictEqual(stats.nFailed, 0); + _assertAllEmailsInvite(actor.restContext, emailsWithUpperCase, { shouldInvite: true }, () => { + // Run the migration + LowerCaseEmailsMigrator.doMigration((err, stats) => { + assert.ok(!err); + assert.strictEqual(stats.nUpdated, emailsWithUpperCase.length); + assert.strictEqual(stats.nFailed, 0); - // Refresh the search index - SearchTestUtil.reindexAll(globalAdminRestContext, () => { - // Ensure the emails are now all searchable and can be matched - // when sharing - _assertAllEmailsSearch( + // Refresh the search index + SearchTestUtil.reindexAll(globalAdminRestContext, () => { + // Ensure the emails are now all searchable and can be matched + // when sharing + _assertAllEmailsSearch(emailsWithUpperCase, { shouldContain: true }, () => { + _assertAllEmailsInvite( + actor.restContext, emailsWithUpperCase, - { shouldContain: true }, + { shouldInvite: false }, () => { - _assertAllEmailsInvite( - actor.restContext, - emailsWithUpperCase, - { shouldInvite: false }, - () => { - return callback(); - } - ); + return callback(); } ); }); }); - } - ); + }); + }); }); }); }); diff --git a/packages/oae-principals/tests/test-push.js b/packages/oae-principals/tests/test-push.js index 24785b0636..e8b47e07d5 100644 --- a/packages/oae-principals/tests/test-push.js +++ b/packages/oae-principals/tests/test-push.js @@ -13,17 +13,15 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityTestsUtil = require('oae-activity/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const TestsUtil = require('oae-tests'); +import * as ActivityTestsUtil from 'oae-activity/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; -const { PrincipalsConstants } = require('oae-principals/lib/constants'); +import { ActivityConstants } from 'oae-activity/lib/constants'; +import { PrincipalsConstants } from 'oae-principals/lib/constants'; describe('Group Push', () => { // Rest contexts that can be used performing rest requests @@ -34,9 +32,7 @@ describe('Group Push', () => { * Function that will fill up the tenant admin and anymous rest contexts */ before(callback => { - localAdminRestContext = TestsUtil.createTenantAdminRestContext( - global.oaeTests.tenants.localhost.host - ); + localAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.localhost.host); anonymousRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); callback(); }); @@ -85,70 +81,48 @@ describe('Group Push', () => { assert.strictEqual(err.code, 400); // Ensure we get a 401 error with an invalid token - client.subscribe( - group.id, - 'activity', - { signature: group.signature.signature }, - null, - err => { + client.subscribe(group.id, 'activity', { signature: group.signature.signature }, null, err => { + assert.strictEqual(err.code, 401); + client.subscribe(group.id, 'activity', { expires: group.signature.expires }, null, err => { assert.strictEqual(err.code, 401); client.subscribe( group.id, 'activity', - { expires: group.signature.expires }, + { + expires: Date.now() + 10000, + signature: 'foo', + lastModified: Date.now() + }, null, err => { assert.strictEqual(err.code, 401); - client.subscribe( - group.id, - 'activity', - { - expires: Date.now() + 10000, - signature: 'foo', - lastModified: Date.now() - }, - null, - err => { + + // Simon should not be able to use a signature that was generated for Branden + RestAPI.Group.getGroup(branden.restContext, group.id, (err, groupForBranden) => { + assert.ok(!err); + client.subscribe(group.id, 'activity', groupForBranden.signature, null, err => { assert.strictEqual(err.code, 401); - // Simon should not be able to use a signature that was generated for Branden - RestAPI.Group.getGroup( - branden.restContext, + // Sanity check + client.subscribe( group.id, - (err, groupForBranden) => { + 'activity', + { + expires: group.signature.expires, + signature: group.signature.signature + }, + null, + err => { assert.ok(!err); - client.subscribe( - group.id, - 'activity', - groupForBranden.signature, - null, - err => { - assert.strictEqual(err.code, 401); - - // Sanity check - client.subscribe( - group.id, - 'activity', - { - expires: group.signature.expires, - signature: group.signature.signature - }, - null, - err => { - assert.ok(!err); - return callback(); - } - ); - } - ); + return callback(); } ); - } - ); + }); + }); } ); - } - ); + }); + }); }); }); }); @@ -247,34 +221,29 @@ describe('Group Push', () => { assert.ok(!err); // Route and deliver activities - ActivityTestsUtil.collectAndGetActivityStream( - contexts.simon.restContext, - null, - null, - () => { - // Register for some streams - const data = { - authentication: { - userId: contexts.simon.user.id, - tenantAlias: simonFull.tenant.alias, - signature: simonFull.signature - }, - streams: [ - { - resourceId: group.id, - streamType: 'activity', - token: group.signature - } - ] - }; - - ActivityTestsUtil.getFullySetupPushClient(data, client => { - setTimeout(() => { - callback(contexts, group, client); - }, 2000); - }); - } - ); + ActivityTestsUtil.collectAndGetActivityStream(contexts.simon.restContext, null, null, () => { + // Register for some streams + const data = { + authentication: { + userId: contexts.simon.user.id, + tenantAlias: simonFull.tenant.alias, + signature: simonFull.signature + }, + streams: [ + { + resourceId: group.id, + streamType: 'activity', + token: group.signature + } + ] + }; + + ActivityTestsUtil.getFullySetupPushClient(data, client => { + setTimeout(() => { + callback(contexts, group, client); + }, 2000); + }); + }); }); } ); @@ -288,14 +257,9 @@ describe('Group Push', () => { it('verify updates trigger a push notification', callback => { setupFixture((contexts, group, client) => { // Trigger an update - RestAPI.Group.updateGroup( - contexts.branden.restContext, - group.id, - { displayName: 'Laaike whatevs' }, - err => { - assert.ok(!err); - } - ); + RestAPI.Group.updateGroup(contexts.branden.restContext, group.id, { displayName: 'Laaike whatevs' }, err => { + assert.ok(!err); + }); client.on('message', message => { if (message.resourceId === group.id && message.streamType === 'activity') { @@ -322,14 +286,9 @@ describe('Group Push', () => { it('verify visibility updates trigger a push notification', callback => { setupFixture((contexts, group, client) => { // Trigger an update - RestAPI.Group.updateGroup( - contexts.branden.restContext, - group.id, - { visibility: 'loggedin' }, - err => { - assert.ok(!err); - } - ); + RestAPI.Group.updateGroup(contexts.branden.restContext, group.id, { visibility: 'loggedin' }, err => { + assert.ok(!err); + }); client.on('message', message => { ActivityTestsUtil.assertActivity( @@ -358,10 +317,7 @@ describe('Group Push', () => { // We should receive 2 messages. One for adding Nico to the group and one for changing his role client.on('message', message => { - if ( - message.activities[0]['oae:activityType'] === - PrincipalsConstants.activity.ACTIVITY_GROUP_ADD_MEMBER - ) { + if (message.activities[0]['oae:activityType'] === PrincipalsConstants.activity.ACTIVITY_GROUP_ADD_MEMBER) { ActivityTestsUtil.assertActivity( message.activities[0], PrincipalsConstants.activity.ACTIVITY_GROUP_ADD_MEMBER, @@ -372,8 +328,7 @@ describe('Group Push', () => { ); addedActivityReceived = true; } else if ( - message.activities[0]['oae:activityType'] === - PrincipalsConstants.activity.ACTIVITY_GROUP_UPDATE_MEMBER_ROLE + message.activities[0]['oae:activityType'] === PrincipalsConstants.activity.ACTIVITY_GROUP_UPDATE_MEMBER_ROLE ) { ActivityTestsUtil.assertActivity( message.activities[0], @@ -398,31 +353,16 @@ describe('Group Push', () => { assert.ok(!err); // Route and deliver activities - ActivityTestsUtil.collectAndGetActivityStream( - contexts.simon.restContext, - null, - null, - () => { - // Changing nico's role to a manager should result in a message on the socket as well - membersToAdd[contexts.nico.user.id] = 'manager'; - RestAPI.Group.setGroupMembers( - contexts.branden.restContext, - group.id, - membersToAdd, - err => { - assert.ok(!err); + ActivityTestsUtil.collectAndGetActivityStream(contexts.simon.restContext, null, null, () => { + // Changing nico's role to a manager should result in a message on the socket as well + membersToAdd[contexts.nico.user.id] = 'manager'; + RestAPI.Group.setGroupMembers(contexts.branden.restContext, group.id, membersToAdd, err => { + assert.ok(!err); - // Route and deliver activities - ActivityTestsUtil.collectAndGetActivityStream( - contexts.simon.restContext, - null, - null, - () => {} - ); - } - ); - } - ); + // Route and deliver activities + ActivityTestsUtil.collectAndGetActivityStream(contexts.simon.restContext, null, null, () => {}); + }); + }); }); }); }); diff --git a/packages/oae-principals/tests/test-search.js b/packages/oae-principals/tests/test-search.js index 4823fef35d..841970c2b4 100644 --- a/packages/oae-principals/tests/test-search.js +++ b/packages/oae-principals/tests/test-search.js @@ -12,15 +12,14 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert' +import _ from 'underscore' -const ElasticSearch = require('oae-search/lib/internal/elasticsearch'); -const RestAPI = require('oae-rest'); -const SearchAPI = require('oae-search'); -const { SearchConstants } = require('oae-search/lib/constants'); -const SearchTestsUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests'); +import * as ElasticSearch from 'oae-search/lib/internal/elasticsearch'; +import * as RestAPI from 'oae-rest'; +import * as SearchAPI from 'oae-search'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests'; describe('Search', () => { // REST contexts we can use to do REST requests diff --git a/packages/oae-principals/tests/test-terms-and-conditions.js b/packages/oae-principals/tests/test-terms-and-conditions.js index a09aea4242..c1a62b09e3 100644 --- a/packages/oae-principals/tests/test-terms-and-conditions.js +++ b/packages/oae-principals/tests/test-terms-and-conditions.js @@ -13,14 +13,13 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const ConfigTestUtil = require('oae-config/lib/test/util'); -const { Context } = require('oae-context'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const TestsUtil = require('oae-tests'); +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import { RestContext } from 'oae-rest/lib/model'; describe('Terms and Conditions', () => { // Rest context that can be used every time we need to make a request as a global admin @@ -44,23 +43,13 @@ describe('Terms and Conditions', () => { * Function that will disable and clear the Terms and Conditions after each test */ afterEach(callback => { - ConfigTestUtil.clearConfigAndWait( - camAdminRestContext, - null, - ['oae-principals/termsAndConditions/enabled'], - err => { + ConfigTestUtil.clearConfigAndWait(camAdminRestContext, null, ['oae-principals/termsAndConditions/enabled'], err => { + assert.ok(!err); + ConfigTestUtil.clearConfigAndWait(camAdminRestContext, null, ['oae-principals/termsAndConditions/text'], err => { assert.ok(!err); - ConfigTestUtil.clearConfigAndWait( - camAdminRestContext, - null, - ['oae-principals/termsAndConditions/text'], - err => { - assert.ok(!err); - return callback(); - } - ); - } - ); + return callback(); + }); + }); }); /** @@ -105,10 +94,7 @@ describe('Terms and Conditions', () => { enableAndSetTC(camAdminRestContext, 'default', 'legalese', true, () => { // Not passing in acceptedTC: true should result in a 400 const username = TestsUtil.generateRandomText(5); - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); RestAPI.User.createUser( anonymousCamRestContext, username, @@ -203,169 +189,126 @@ describe('Terms and Conditions', () => { assert.ok(data.needsToAcceptTC); // Verify there is nothing in this user's library - RestAPI.Content.getLibrary( - mrvisser.restContext, - mrvisser.user.id, - null, - 10, - (err, library) => { + RestAPI.Content.getLibrary(mrvisser.restContext, mrvisser.user.id, null, 10, (err, library) => { + assert.ok(!err); + assert.strictEqual(library.results.length, 0); + + // Accept the Terms and Conditions for the cam tenant. + RestAPI.User.acceptTermsAndConditions(mrvisser.restContext, mrvisser.user.id, err => { assert.ok(!err); - assert.strictEqual(library.results.length, 0); - // Accept the Terms and Conditions for the cam tenant. - RestAPI.User.acceptTermsAndConditions( - mrvisser.restContext, - mrvisser.user.id, - err => { - assert.ok(!err); + RestAPI.User.getMe(mrvisser.restContext, (err, data) => { + assert.ok(!err); + assert.ok(!data.needsToAcceptTC); - RestAPI.User.getMe(mrvisser.restContext, (err, data) => { + // Verify that it is now possible to perform POST requests + RestAPI.Content.createLink( + mrvisser.restContext, + 'Yahoo', + 'Yahoo', + 'public', + 'http://uk.yahoo.com', + [], + [], + [], + (err, createdLink) => { assert.ok(!err); - assert.ok(!data.needsToAcceptTC); - - // Verify that it is now possible to perform POST requests - RestAPI.Content.createLink( - mrvisser.restContext, - 'Yahoo', - 'Yahoo', - 'public', - 'http://uk.yahoo.com', - [], - [], - [], - (err, createdLink) => { - assert.ok(!err); - RestAPI.Content.getLibrary( - mrvisser.restContext, - mrvisser.user.id, - null, - 10, - (err, library) => { - assert.ok(!err); - assert.strictEqual(library.results.length, 1); - - // Update the Terms and Conditions, ensuring we wait long enough to get a new timestamp - setTimeout( - enableAndSetTC, - 500, - camAdminRestContext, - 'default', - 'new legalese', - true, - () => { - // Mrvisser needs to re-accept the Terms and Conditions before he can continue working on the system - RestAPI.User.getMe(mrvisser.restContext, (err, data) => { - assert.ok(!err); - assert.ok(data.needsToAcceptTC); - - RestAPI.Content.createLink( - mrvisser.restContext, - 'Yahoo', - 'Yahoo', - 'public', - 'http://uk.yahoo.com', - [], - [], - [], - (err, link) => { - assert.ok(err); - assert.strictEqual(err.code, 419); - assert.ok(!link); - - // DELETEs should not be possible - RestAPI.Content.deleteContent( + RestAPI.Content.getLibrary(mrvisser.restContext, mrvisser.user.id, null, 10, (err, library) => { + assert.ok(!err); + assert.strictEqual(library.results.length, 1); + + // Update the Terms and Conditions, ensuring we wait long enough to get a new timestamp + setTimeout(enableAndSetTC, 500, camAdminRestContext, 'default', 'new legalese', true, () => { + // Mrvisser needs to re-accept the Terms and Conditions before he can continue working on the system + RestAPI.User.getMe(mrvisser.restContext, (err, data) => { + assert.ok(!err); + assert.ok(data.needsToAcceptTC); + + RestAPI.Content.createLink( + mrvisser.restContext, + 'Yahoo', + 'Yahoo', + 'public', + 'http://uk.yahoo.com', + [], + [], + [], + (err, link) => { + assert.ok(err); + assert.strictEqual(err.code, 419); + assert.ok(!link); + + // DELETEs should not be possible + RestAPI.Content.deleteContent(mrvisser.restContext, createdLink.id, err => { + assert.ok(err); + assert.strictEqual(err.code, 419); + + // Sanity check that re-accepting it, allows mrvisser to perform POST requests again + setTimeout( + RestAPI.User.acceptTermsAndConditions, + 200, + mrvisser.restContext, + mrvisser.user.id, + err => { + assert.ok(!err); + setTimeout(RestAPI.User.getMe, 1000, mrvisser.restContext, (err, data) => { + assert.ok(!err); + assert.ok(!data.needsToAcceptTC); + + // Verify that the user is now able to perform POST requests + RestAPI.Content.createLink( mrvisser.restContext, - createdLink.id, - err => { - assert.ok(err); - assert.strictEqual(err.code, 419); - - // Sanity check that re-accepting it, allows mrvisser to perform POST requests again - setTimeout( - RestAPI.User.acceptTermsAndConditions, - 200, + 'Yahoo', + 'Yahoo', + 'public', + 'http://uk.yahoo.com', + [], + [], + [], + (err, link) => { + assert.ok(!err); + RestAPI.Content.getLibrary( mrvisser.restContext, mrvisser.user.id, - err => { + null, + 10, + (err, library) => { assert.ok(!err); - setTimeout( - RestAPI.User.getMe, - 1000, - mrvisser.restContext, - (err, data) => { - assert.ok(!err); - assert.ok(!data.needsToAcceptTC); - - // Verify that the user is now able to perform POST requests - RestAPI.Content.createLink( - mrvisser.restContext, - 'Yahoo', - 'Yahoo', - 'public', - 'http://uk.yahoo.com', - [], - [], - [], - (err, link) => { - assert.ok(!err); - RestAPI.Content.getLibrary( - mrvisser.restContext, - mrvisser.user.id, - null, - 10, - (err, library) => { - assert.ok(!err); - assert.strictEqual( - library.results.length, - 2 - ); - - // DELETEs should be possible - RestAPI.Content.deleteContent( - mrvisser.restContext, - link.id, - err => { - assert.ok(!err); - RestAPI.Content.getLibrary( - mrvisser.restContext, - mrvisser.user.id, - null, - 10, - (err, library) => { - assert.ok(!err); - assert.strictEqual( - library.results.length, - 1 - ); - callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + assert.strictEqual(library.results.length, 2); + + // DELETEs should be possible + RestAPI.Content.deleteContent(mrvisser.restContext, link.id, err => { + assert.ok(!err); + RestAPI.Content.getLibrary( + mrvisser.restContext, + mrvisser.user.id, + null, + 10, + (err, library) => { + assert.ok(!err); + assert.strictEqual(library.results.length, 1); + callback(); + } + ); + }); } ); } ); - } - ); - }); - } - ); - } - ); - } - ); - }); - } - ); - } - ); + }); + } + ); + }); + } + ); + }); + }); + }); + } + ); + }); + }); + }); }); } ); @@ -416,76 +359,59 @@ describe('Terms and Conditions', () => { [], (err, link) => { assert.ok(!err); - RestAPI.Content.getLibrary( - mrvisser.restContext, - mrvisser.user.id, - null, - 10, - (err, library) => { - assert.ok(!err); - assert.strictEqual(library.results.length, 1); + RestAPI.Content.getLibrary(mrvisser.restContext, mrvisser.user.id, null, 10, (err, library) => { + assert.ok(!err); + assert.strictEqual(library.results.length, 1); - // Verify that mrvisser is able to perform DELETE requests without having accepted the Terms and Conditions - RestAPI.Content.deleteContent(mrvisser.restContext, link.id, err => { + // Verify that mrvisser is able to perform DELETE requests without having accepted the Terms and Conditions + RestAPI.Content.deleteContent(mrvisser.restContext, link.id, err => { + assert.ok(!err); + RestAPI.Content.getLibrary(mrvisser.restContext, mrvisser.user.id, null, 10, (err, library) => { assert.ok(!err); - RestAPI.Content.getLibrary( - mrvisser.restContext, - mrvisser.user.id, - null, - 10, - (err, library) => { - assert.ok(!err); - assert.strictEqual(library.results.length, 0); - - // Demote mrvisser to a normal user - RestAPI.User.setTenantAdmin( - camAdminRestContext, - mrvisser.user.id, - false, - err => { - assert.ok(!err); + assert.strictEqual(library.results.length, 0); - // Because mrvisser hasn't accepted the Terms and Conditions yet, he cannot interact with the system - // When the user tries to *do* anything, he needs to accept the Terms and Conditions - RestAPI.User.getMe(mrvisser.restContext, (err, data) => { - assert.ok(!err); - assert.ok(data.needsToAcceptTC); - RestAPI.Content.createLink( - mrvisser.restContext, - 'Yahoo', - 'Yahoo', - 'public', - 'http://uk.yahoo.com', - [], - [], - [], - (err, link) => { - assert.ok(err); - assert.strictEqual(err.code, 419); - assert.ok(!link); + // Demote mrvisser to a normal user + RestAPI.User.setTenantAdmin(camAdminRestContext, mrvisser.user.id, false, err => { + assert.ok(!err); - // Verify nothing extra got added to the library - RestAPI.Content.getLibrary( - mrvisser.restContext, - mrvisser.user.id, - null, - 10, - (err, library) => { - assert.ok(!err); - assert.strictEqual(library.results.length, 0); - callback(); - } - ); - } - ); - }); + // Because mrvisser hasn't accepted the Terms and Conditions yet, he cannot interact with the system + // When the user tries to *do* anything, he needs to accept the Terms and Conditions + RestAPI.User.getMe(mrvisser.restContext, (err, data) => { + assert.ok(!err); + assert.ok(data.needsToAcceptTC); + RestAPI.Content.createLink( + mrvisser.restContext, + 'Yahoo', + 'Yahoo', + 'public', + 'http://uk.yahoo.com', + [], + [], + [], + (err, link) => { + assert.ok(err); + assert.strictEqual(err.code, 419); + assert.ok(!link); + + // Verify nothing extra got added to the library + RestAPI.Content.getLibrary( + mrvisser.restContext, + mrvisser.user.id, + null, + 10, + (err, library) => { + assert.ok(!err); + assert.strictEqual(library.results.length, 0); + callback(); + } + ); } ); - } - ); + }); + }); }); - } - ); + }); + }); } ); }); @@ -513,30 +439,18 @@ describe('Terms and Conditions', () => { assert.strictEqual(err.code, 401); // Anonymous users can't accept anything - RestAPI.User.acceptTermsAndConditions( - anonymousCamRestContext, - mrvisser.user.id, - err => { - assert.strictEqual(err.code, 401); - - // Some basic validation - RestAPI.User.acceptTermsAndConditions( - mrvisser.restContext, - 'not a user id', - err => { - assert.strictEqual(err.code, 400); - RestAPI.User.acceptTermsAndConditions( - mrvisser.restContext, - 'g:camtest:not-a-user-id', - err => { - assert.strictEqual(err.code, 400); - callback(); - } - ); - } - ); - } - ); + RestAPI.User.acceptTermsAndConditions(anonymousCamRestContext, mrvisser.user.id, err => { + assert.strictEqual(err.code, 401); + + // Some basic validation + RestAPI.User.acceptTermsAndConditions(mrvisser.restContext, 'not a user id', err => { + assert.strictEqual(err.code, 400); + RestAPI.User.acceptTermsAndConditions(mrvisser.restContext, 'g:camtest:not-a-user-id', err => { + assert.strictEqual(err.code, 400); + callback(); + }); + }); + }); }); }); }); @@ -575,30 +489,22 @@ describe('Terms and Conditions', () => { assert.strictEqual(data.text, 'British English legalese'); // If a locale is specialized for which no Terms and Conditions is available, the default Terms and Conditions should be returned - RestAPI.User.getTermsAndConditions( - mrvisser.restContext, - 'fr_FR', - (err, data) => { - assert.ok(!err); - assert.strictEqual(data.text, 'Default legalese'); + RestAPI.User.getTermsAndConditions(mrvisser.restContext, 'fr_FR', (err, data) => { + assert.ok(!err); + assert.strictEqual(data.text, 'Default legalese'); - // Create an anonymous request context that sends an `Accept-Language: en_CA` header - // to verify that is picked up if no other options are present - const acceptLanguageRestContext = new RestContext('http://localhost:2001', { - hostHeader: global.oaeTests.tenants.cam.host, - additionalHeaders: { 'Accept-Language': 'en-gb' } - }); - RestAPI.User.getTermsAndConditions( - acceptLanguageRestContext, - null, - (err, data) => { - assert.ok(!err); - assert.strictEqual(data.text, 'British English legalese'); - return callback(); - } - ); - } - ); + // Create an anonymous request context that sends an `Accept-Language: en_CA` header + // to verify that is picked up if no other options are present + const acceptLanguageRestContext = new RestContext('http://localhost:2001', { + hostHeader: global.oaeTests.tenants.cam.host, + additionalHeaders: { 'Accept-Language': 'en-gb' } + }); + RestAPI.User.getTermsAndConditions(acceptLanguageRestContext, null, (err, data) => { + assert.ok(!err); + assert.strictEqual(data.text, 'British English legalese'); + return callback(); + }); + }); }); }); }); @@ -616,38 +522,26 @@ describe('Terms and Conditions', () => { // Set a Terms and Conditions enableAndSetTC(camAdminRestContext, 'default', 'Default legalese', true, () => { // Get the Terms and Conditions - setTimeout( - RestAPI.User.getTermsAndConditions, - 200, - anonymousCamRestContext, - null, - (err, firstTC) => { - assert.ok(!err); - assert.strictEqual(firstTC.text, 'Default legalese'); - assert.ok(firstTC.lastUpdate); - assert.ok(firstTC.lastUpdate <= Date.now()); - assert.ok(firstTC.lastUpdate > 0); - - // Update the Terms and Conditions - enableAndSetTC(camAdminRestContext, 'default', 'Other legalese', true, () => { - setTimeout( - RestAPI.User.getTermsAndConditions, - 200, - anonymousCamRestContext, - null, - (err, updatedTC) => { - assert.ok(!err); - assert.strictEqual(updatedTC.text, 'Other legalese'); - assert.ok(updatedTC.lastUpdate); - assert.ok(updatedTC.lastUpdate <= Date.now()); - assert.ok(updatedTC.lastUpdate > 0); - assert.ok(updatedTC.lastUpdate > firstTC.lastUpdate); - callback(); - } - ); + setTimeout(RestAPI.User.getTermsAndConditions, 200, anonymousCamRestContext, null, (err, firstTC) => { + assert.ok(!err); + assert.strictEqual(firstTC.text, 'Default legalese'); + assert.ok(firstTC.lastUpdate); + assert.ok(firstTC.lastUpdate <= Date.now()); + assert.ok(firstTC.lastUpdate > 0); + + // Update the Terms and Conditions + enableAndSetTC(camAdminRestContext, 'default', 'Other legalese', true, () => { + setTimeout(RestAPI.User.getTermsAndConditions, 200, anonymousCamRestContext, null, (err, updatedTC) => { + assert.ok(!err); + assert.strictEqual(updatedTC.text, 'Other legalese'); + assert.ok(updatedTC.lastUpdate); + assert.ok(updatedTC.lastUpdate <= Date.now()); + assert.ok(updatedTC.lastUpdate > 0); + assert.ok(updatedTC.lastUpdate > firstTC.lastUpdate); + callback(); }); - } - ); + }); + }); }); }); @@ -675,15 +569,11 @@ describe('Terms and Conditions', () => { assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); // Global admins should be able to see it - RestAPI.Config.getTenantConfig( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - (err, config) => { - assert.ok(!err); - assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); - return callback(); - } - ); + RestAPI.Config.getTenantConfig(globalAdminRestContext, global.oaeTests.tenants.cam.alias, (err, config) => { + assert.ok(!err); + assert.ok(_.isObject(config['oae-principals'].termsAndConditions.text)); + return callback(); + }); }); }); }); diff --git a/packages/oae-principals/tests/test-users.js b/packages/oae-principals/tests/test-users.js index 9bfc9a1655..a18c4bb387 100644 --- a/packages/oae-principals/tests/test-users.js +++ b/packages/oae-principals/tests/test-users.js @@ -13,26 +13,27 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const util = require('util'); -const _ = require('underscore'); - -const AuthenticationAPI = require('oae-authentication'); -const AuthzInvitationsDAO = require('oae-authz/lib/invitations/dao'); -const AuthzUtil = require('oae-authz/lib/util'); -const ConfigTestUtil = require('oae-config/lib/test/util'); -const { Context } = require('oae-context'); -const EmailTestUtil = require('oae-email/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const { Tenant } = require('oae-tenants/lib/model'); -const TenantsTestUtil = require('oae-tenants/lib/test/util'); -const TestsUtil = require('oae-tests'); -const TZ = require('oae-util/lib/tz'); - -const PrincipalsAPI = require('oae-principals'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); +import assert from 'assert'; +import fs from 'fs'; +import util from 'util'; +import _ from 'underscore'; + +import * as AuthenticationAPI from 'oae-authentication'; +import * as AuthzInvitationsDAO from 'oae-authz/lib/invitations/dao'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as EmailTestUtil from 'oae-email/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; +import * as TestsUtil from 'oae-tests'; +import * as TZ from 'oae-util/lib/tz'; +import * as PrincipalsAPI from 'oae-principals'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; + +import { RestContext } from 'oae-rest/lib/model'; +import { Tenant } from 'oae-tenants/lib/model'; +import { Context } from 'oae-context'; + describe('Users', () => { // Rest contexts that can be used to make requests as different types of users diff --git a/packages/oae-principals/tests/test-util.js b/packages/oae-principals/tests/test-util.js index a960859166..36246b7b38 100644 --- a/packages/oae-principals/tests/test-util.js +++ b/packages/oae-principals/tests/test-util.js @@ -12,17 +12,17 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); -const AuthzUtil = require('oae-authz/lib/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const { Context } = require('oae-context'); -const TestsUtil = require('oae-tests'); +import assert from 'assert'; +import _ from 'underscore'; -const PrincipalsUtil = require('oae-principals/lib/util'); -const { User } = require('oae-principals/lib/model'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; + +import { Context } from 'oae-context'; +import { User } from 'oae-principals/lib/model'; describe('Principals', () => { // Rest context that can be used every time we need to make a request as a global admin @@ -61,43 +61,23 @@ describe('Principals', () => { const createPrincipal = function(type, identifier, metadata) { const principalId = TestsUtil.generateTestUserId(identifier); if (type === 'group') { - RestAPI.Group.createGroup( - johnRestContext, - metadata, - metadata, - 'public', - 'yes', - [], - [], - (err, groupObj) => { - assert.ok(!err); - createdPrincipals[identifier] = groupObj; - if (_.keys(createdPrincipals).length === 7) { - callback(createdPrincipals); - } + RestAPI.Group.createGroup(johnRestContext, metadata, metadata, 'public', 'yes', [], [], (err, groupObj) => { + assert.ok(!err); + createdPrincipals[identifier] = groupObj; + if (_.keys(createdPrincipals).length === 7) { + callback(createdPrincipals); } - ); + }); } else { - const email = TestsUtil.generateTestEmailAddress( - null, - global.oaeTests.tenants.cam.emailDomains[0] - ); - RestAPI.User.createUser( - camAdminRestContext, - principalId, - 'password', - metadata, - email, - {}, - (err, userObj) => { - assert.ok(!err); - const userContext = new Context(global.oaeTests.tenants.cam, userObj); - createdPrincipals[identifier] = userContext; - if (_.keys(createdPrincipals).length === 7) { - callback(createdPrincipals); - } + const email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomains[0]); + RestAPI.User.createUser(camAdminRestContext, principalId, 'password', metadata, email, {}, (err, userObj) => { + assert.ok(!err); + const userContext = new Context(global.oaeTests.tenants.cam, userObj); + createdPrincipals[identifier] = userContext; + if (_.keys(createdPrincipals).length === 7) { + callback(createdPrincipals); } - ); + }); } }; @@ -119,62 +99,43 @@ describe('Principals', () => { it('verify get principal', callback => { createUsersAndGroup(createdPrincipals => { // Get an existing user - PrincipalsUtil.getPrincipal( - createdPrincipals.nicolaas, - createdPrincipals.nicolaas.user().id, - (err, user) => { - assert.ok(!err); - assert.ok(user); - assert.strictEqual(user.id, createdPrincipals.nicolaas.user().id); - assert.strictEqual(user.displayName, 'Nicolaas Matthijs'); - assert.strictEqual(user.resourceType, 'user'); - assert.strictEqual( - user.profilePath, - '/user/' + user.tenant.alias + '/' + AuthzUtil.getResourceFromId(user.id).resourceId - ); + PrincipalsUtil.getPrincipal(createdPrincipals.nicolaas, createdPrincipals.nicolaas.user().id, (err, user) => { + assert.ok(!err); + assert.ok(user); + assert.strictEqual(user.id, createdPrincipals.nicolaas.user().id); + assert.strictEqual(user.displayName, 'Nicolaas Matthijs'); + assert.strictEqual(user.resourceType, 'user'); + assert.strictEqual( + user.profilePath, + '/user/' + user.tenant.alias + '/' + AuthzUtil.getResourceFromId(user.id).resourceId + ); - // Get a non-existing user - PrincipalsUtil.getPrincipal( - createdPrincipals.nicolaas, - 'non-existing-user', - (err, user) => { - assert.ok(err); - assert.ok(!user); + // Get a non-existing user + PrincipalsUtil.getPrincipal(createdPrincipals.nicolaas, 'non-existing-user', (err, user) => { + assert.ok(err); + assert.ok(!user); - // Get an existing group - PrincipalsUtil.getPrincipal( - createdPrincipals.nicolaas, - createdPrincipals['oae-team'].id, - (err, group) => { - assert.ok(!err); - assert.ok(group); - assert.strictEqual(group.id, createdPrincipals['oae-team'].id); - assert.strictEqual(group.displayName, 'OAE Team'); - assert.strictEqual(group.resourceType, 'group'); - assert.strictEqual( - group.profilePath, - '/group/' + - group.tenant.alias + - '/' + - AuthzUtil.getResourceFromId(group.id).resourceId - ); - - // Get a non-existing group - PrincipalsUtil.getPrincipal( - createdPrincipals.nicolaas, - 'non-existing-group', - (err, group) => { - assert.ok(err); - assert.ok(!group); - callback(); - } - ); - } - ); - } - ); - } - ); + // Get an existing group + PrincipalsUtil.getPrincipal(createdPrincipals.nicolaas, createdPrincipals['oae-team'].id, (err, group) => { + assert.ok(!err); + assert.ok(group); + assert.strictEqual(group.id, createdPrincipals['oae-team'].id); + assert.strictEqual(group.displayName, 'OAE Team'); + assert.strictEqual(group.resourceType, 'group'); + assert.strictEqual( + group.profilePath, + '/group/' + group.tenant.alias + '/' + AuthzUtil.getResourceFromId(group.id).resourceId + ); + + // Get a non-existing group + PrincipalsUtil.getPrincipal(createdPrincipals.nicolaas, 'non-existing-group', (err, group) => { + assert.ok(err); + assert.ok(!group); + callback(); + }); + }); + }); + }); }); }); @@ -187,27 +148,14 @@ describe('Principals', () => { // Get existing users PrincipalsUtil.getPrincipals( createdPrincipals.nicolaas, - [ - createdPrincipals.nicolaas.user().id, - createdPrincipals.simon.user().id, - createdPrincipals.bert.user().id - ], + [createdPrincipals.nicolaas.user().id, createdPrincipals.simon.user().id, createdPrincipals.bert.user().id], (err, users) => { assert.ok(!err); assert.ok(users); assert.strictEqual(_.keys(users).length, 3); - assert.strictEqual( - users[createdPrincipals.nicolaas.user().id].id, - createdPrincipals.nicolaas.user().id - ); - assert.strictEqual( - users[createdPrincipals.simon.user().id].id, - createdPrincipals.simon.user().id - ); - assert.strictEqual( - users[createdPrincipals.bert.user().id].id, - createdPrincipals.bert.user().id - ); + assert.strictEqual(users[createdPrincipals.nicolaas.user().id].id, createdPrincipals.nicolaas.user().id); + assert.strictEqual(users[createdPrincipals.simon.user().id].id, createdPrincipals.simon.user().id); + assert.strictEqual(users[createdPrincipals.bert.user().id].id, createdPrincipals.bert.user().id); // Get existing groups PrincipalsUtil.getPrincipals( @@ -217,10 +165,7 @@ describe('Principals', () => { assert.ok(!err); assert.ok(groups); assert.strictEqual(_.keys(groups).length, 2); - assert.strictEqual( - groups[createdPrincipals['oae-team'].id].id, - createdPrincipals['oae-team'].id - ); + assert.strictEqual(groups[createdPrincipals['oae-team'].id].id, createdPrincipals['oae-team'].id); assert.strictEqual( groups[createdPrincipals['backend-team'].id].id, createdPrincipals['backend-team'].id diff --git a/packages/oae-resource/lib/actions.js b/packages/oae-resource/lib/actions.js index e16d6c1747..8ef9437903 100644 --- a/packages/oae-resource/lib/actions.js +++ b/packages/oae-resource/lib/actions.js @@ -13,25 +13,27 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); - -const AuthzAPI = require('oae-authz'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzInvitationsDAO = require('oae-authz/lib/invitations/dao'); -const AuthzModel = require('oae-authz/lib/model'); -const AuthzPermissions = require('oae-authz/lib/permissions'); -const AuthzUtil = require('oae-authz/lib/util'); -const EmitterAPI = require('oae-emitter'); -const { Invitation } = require('oae-authz/lib/invitations/model'); -const OaeUtil = require('oae-util/lib/util'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const PrincipalsUtil = require('oae-principals/lib/util'); -const { Validator } = require('oae-authz/lib/validator'); - -const ResourceActivity = require('oae-resource/lib/activity'); -const { ResourceConstants } = require('oae-resource/lib/constants'); - -const log = require('oae-logger').logger('oae-resource-actions'); +import _ from 'underscore'; + +import { logger } from 'oae-logger'; + +import * as AuthzAPI from 'oae-authz'; +import * as AuthzInvitationsDAO from 'oae-authz/lib/invitations/dao'; +import * as AuthzModel from 'oae-authz/lib/model'; +import * as AuthzPermissions from 'oae-authz/lib/permissions'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as EmitterAPI from 'oae-emitter'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; +import * as ResourceActivity from 'oae-resource/lib/activity'; + +import { Invitation } from 'oae-authz/lib/invitations/model'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { Validator } from 'oae-authz/lib/validator'; +import { ResourceConstants } from 'oae-resource/lib/constants'; + +const log = logger('oae-resource-actions'); const ResourceActions = new EmitterAPI.EventEmitter(); @@ -687,6 +689,7 @@ const _applyAllMemberChanges = function(memberRolesByResourceId, callback) { log().warn({ err }, 'An error occurred computing member role changes when an invitation was accepted'); return _done(); } + if (_.isEmpty(idChangeInfo.changes)) { // Ignore any resource where its invitation change should not be applied return _done(); @@ -811,11 +814,4 @@ const _emitInvited = function(ctx, resource, emailRoles, emailTokens, callback) }); }; -module.exports = { - emitter: ResourceActions, - create, - share, - setRoles, - resendInvitation, - acceptInvitation -}; +export { ResourceActions as emitter, create, share, setRoles, resendInvitation, acceptInvitation }; diff --git a/packages/oae-resource/lib/activity.js b/packages/oae-resource/lib/activity.js index 7878b46a19..3af2280442 100644 --- a/packages/oae-resource/lib/activity.js +++ b/packages/oae-resource/lib/activity.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -const ActivityAPI = require('oae-activity'); -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityModel = require('oae-activity/lib/model'); -const { AuthzConstants } = require('oae-authz/lib/constants'); +import * as ActivityAPI from 'oae-activity'; +import * as ActivityModel from 'oae-activity/lib/model'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { ActivityConstants } from 'oae-activity/lib/constants'; /** * Post the "accept invitation" activity for the given context and target resource @@ -46,6 +46,4 @@ const postInvitationAcceptActivity = function(ctx, resource, inviterUser) { ActivityAPI.postActivity(ctx, activitySeed); }; -module.exports = { - postInvitationAcceptActivity -}; +export { postInvitationAcceptActivity }; diff --git a/packages/oae-resource/lib/constants.js b/packages/oae-resource/lib/constants.js index 901366904f..0707a28627 100644 --- a/packages/oae-resource/lib/constants.js +++ b/packages/oae-resource/lib/constants.js @@ -20,4 +20,4 @@ ResourceConstants.events = { INVITED: 'invited' }; -module.exports = { ResourceConstants }; +export { ResourceConstants }; diff --git a/packages/oae-resource/lib/rest.js b/packages/oae-resource/lib/rest.js index f4658dbbcb..58ef89e79e 100644 --- a/packages/oae-resource/lib/rest.js +++ b/packages/oae-resource/lib/rest.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const OAE = require('oae-util/lib/oae'); +import * as OAE from 'oae-util/lib/oae'; -const ResourceActions = require('oae-resource/lib/actions'); +import * as ResourceActions from 'oae-resource/lib/actions'; /** * @REST postInvitationAccept diff --git a/packages/oae-search/lib/api.js b/packages/oae-search/lib/api.js index e78598e96d..e534a946bb 100644 --- a/packages/oae-search/lib/api.js +++ b/packages/oae-search/lib/api.js @@ -13,17 +13,19 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; +import { logger } from 'oae-logger'; -const EmitterAPI = require('oae-emitter'); -const log = require('oae-logger').logger('oae-search'); -const TaskQueue = require('oae-util/lib/taskqueue'); -const { Validator } = require('oae-util/lib/validator'); +import * as EmitterAPI from 'oae-emitter'; +import * as TaskQueue from 'oae-util/lib/taskqueue'; +import * as SearchUtil from 'oae-search/lib/util'; -const { SearchConstants } = require('oae-search/lib/constants'); -const { SearchResult } = require('oae-search/lib/model'); -const SearchUtil = require('oae-search/lib/util'); -const client = require('./internal/elasticsearch'); +import { Validator } from 'oae-util/lib/validator'; +import { SearchConstants } from 'oae-search/lib/constants'; +import { SearchResult } from 'oae-search/lib/model'; +import * as client from './internal/elasticsearch'; + +const log = logger('oae-search'); // Holds the currently configured index to which we will perform all requested operations, as per // the `config.search.index` configuration object in config.js @@ -125,6 +127,7 @@ const registerSearchDocumentTransformer = function(typeName, transformer) { if (searchDocumentTransformers[typeName]) { throw new Error('Document transformer for type ' + typeName + ' already exists'); } + searchDocumentTransformers[typeName] = transformer; }; @@ -145,6 +148,7 @@ const registerSearchDocumentProducer = function(typeName, producer) { if (searchDocumentProducers[typeName]) { throw new Error('Document producer for type ' + typeName + ' already exists'); } + searchDocumentProducers[typeName] = producer; }; @@ -175,6 +179,7 @@ const registerSearch = function(typeName, queryBuilder, postProcessor) { if (searches[typeName]) { throw new Error('Search type ' + typeName + ' already exists'); } + searches[typeName] = { queryBuilder, postProcessor: @@ -197,6 +202,7 @@ const registerReindexAllHandler = function(handlerId, handler) { if (reindexAllHandlers[handlerId]) { throw new Error('Reindex-all handler with id ' + handlerId + ' already exists'); } + reindexAllHandlers[handlerId] = handler; }; @@ -326,6 +332,7 @@ const search = function(ctx, searchType, opts, callback) { if (err) { return callback(err); } + if (!queryData) { return callback(null, new SearchResult(0, [])); } @@ -509,6 +516,7 @@ const _ensureSearchSchema = function(callback, _names) { return _ensureSearchSchema(callback, _.keys(childSearchDocuments)); }); } + if (_.isEmpty(_names)) { return callback(); } @@ -578,6 +586,7 @@ const _handleReindexAllTask = function(data, callback) { // Do nothing, we've already returned to the caller return; } + if (err) { complete = true; return callback(err); @@ -690,6 +699,7 @@ const _deleteAll = function(deletes, callback) { if (del.deleteType === 'id') { return client.del(del.documentType, del.id, _handleDocumentsDeleted); } + if (del.deleteType === 'query') { const query = { query: del.query }; return client.deleteByQuery(del.documentType, query, null, _handleDocumentsDeleted); @@ -767,6 +777,7 @@ const _handleIndexDocumentTask = function(data, callback) { if (_.isEmpty(allDocs)) { return callback(); } + if (allDocs.length > 1) { const ops = SearchUtil.createBulkIndexOperations(allDocs); client.bulk(ops, err => { @@ -791,7 +802,7 @@ const _handleIndexDocumentTask = function(data, callback) { delete doc.id; delete doc._parent; - client.index(doc._type, id, doc, opts, err => { + client.runIndex(doc._type, id, doc, opts, err => { if (err) { log().error({ err, id, doc, opts }, 'Error indexing a document'); } else { @@ -899,8 +910,8 @@ const _produceAllChildDocuments = function(resourceChildrenToIndex, callback, _d } }; -module.exports = { - emitter: SearchAPI, +export { + SearchAPI as emitter, registerSearchDocumentTransformer, registerSearchDocumentProducer, registerSearch, diff --git a/packages/oae-search/lib/constants.js b/packages/oae-search/lib/constants.js index 2ed73dbf8d..d1e8a682ed 100644 --- a/packages/oae-search/lib/constants.js +++ b/packages/oae-search/lib/constants.js @@ -85,4 +85,4 @@ SearchConstants.events = { SEARCH: 'search' }; -module.exports = { SearchConstants }; +export { SearchConstants }; diff --git a/packages/oae-search/lib/init.js b/packages/oae-search/lib/init.js index f2c3ff0488..f02620defc 100644 --- a/packages/oae-search/lib/init.js +++ b/packages/oae-search/lib/init.js @@ -13,18 +13,20 @@ * permissions and limitations under the License. */ -const SearchAPI = require('oae-search'); +import * as SearchAPI from 'oae-search'; +import generalSearch from './searches/general'; +import deletedSearch from './searches/deleted'; +import { queryBuilder, postProcessor } from './searches/email'; -module.exports = function(config, callback) { - // const { index, hosts } = config.search; +export function init(config, callback) { + // Const { index, hosts } = config.search; const destroy = config.search.index.destroyOnStartup === true; // Register generic search endpoints - SearchAPI.registerSearch('general', require('./searches/general')); - SearchAPI.registerSearch('deleted', require('./searches/deleted')); + SearchAPI.registerSearch('general', generalSearch); + SearchAPI.registerSearch('deleted', deletedSearch); - const emailSearch = require('./searches/email'); - SearchAPI.registerSearch('email', emailSearch.queryBuilder, emailSearch.postProcessor); + SearchAPI.registerSearch('email', queryBuilder, postProcessor); SearchAPI.refreshSearchConfiguration(config.search, err => { if (err) { @@ -34,4 +36,4 @@ module.exports = function(config, callback) { // Build the index and seed the search schema return SearchAPI.buildIndex(destroy, callback); }); -}; +} diff --git a/packages/oae-search/lib/internal/elasticsearch.js b/packages/oae-search/lib/internal/elasticsearch.js index 005243d1d8..aa4de207ac 100644 --- a/packages/oae-search/lib/internal/elasticsearch.js +++ b/packages/oae-search/lib/internal/elasticsearch.js @@ -13,10 +13,15 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const ElasticSearchClient = require('elasticsearchclient'); -const log = require('oae-logger').logger('elasticsearchclient'); -const Telemetry = require('oae-telemetry').telemetry('search'); +import _ from 'underscore'; +import { logger } from 'oae-logger'; + +import ElasticSearchClient from 'elasticsearchclient'; +import { telemetry } from 'oae-telemetry'; + +const log = logger('elasticsearchclient'); + +const Telemetry = telemetry('search'); let index = null; let client = null; @@ -58,6 +63,7 @@ const createIndex = function(indexName, settings, callback) { if (exists) { return callback(); } + log().info( { indexName, @@ -105,12 +111,14 @@ const indexExists = function(indexName, callback) { if (err && err.error === 'IndexMissingException[[' + indexName + '] missing]') { return callback(null, false); } + if (err) { const timeout = 5; log().error('Error connecting to elasticsearch, retrying in ' + timeout + 's...'); return setTimeout(indexExists, timeout * 1000, indexName, callback); } + return callback(null, true); }; @@ -147,6 +155,7 @@ const putMapping = function(typeName, fieldProperties, opts, callback) { if (err) { return callback(err); } + if (exists) { return callback(); } @@ -210,7 +219,7 @@ const search = function(query, options, callback) { * @param {Function} callback Standard callback function * @param {Object} callback.err An error that occurred, if any */ -index = function(typeName, id, doc, options, callback) { +const runIndex = function(typeName, id, doc, options, callback) { log().trace({ typeName, id, document: doc, options }, 'Indexing a document'); return _exec('index', client.index(index, typeName, doc, id, options), callback); }; @@ -331,7 +340,7 @@ const _logError = function(callName, err) { log().error({ err }, 'Error executing %s query.', callName); }; -module.exports = { +export { refreshSearchConfiguration, createIndex, deleteIndex, @@ -340,7 +349,7 @@ module.exports = { putMapping, mappingExists, search, - index, + runIndex, bulk, del, deleteByQuery diff --git a/packages/oae-search/lib/model.js b/packages/oae-search/lib/model.js index 1f6805c1ec..b59a3b91ec 100644 --- a/packages/oae-search/lib/model.js +++ b/packages/oae-search/lib/model.js @@ -37,4 +37,4 @@ const SearchResult = function(total, results) { return that; }; -module.exports = { SearchResult }; +export { SearchResult }; diff --git a/packages/oae-search/lib/rest.js b/packages/oae-search/lib/rest.js index c862b73082..8a71584f31 100644 --- a/packages/oae-search/lib/rest.js +++ b/packages/oae-search/lib/rest.js @@ -12,15 +12,14 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const OAE = require('oae-util/lib/oae'); -const OaeUtil = require('oae-util/lib/util'); -const TenantsAPI = require('oae-tenants/lib/api'); - -const SearchAPI = require('oae-search'); -const SearchUtil = require('oae-search/lib/util'); +import * as OAE from 'oae-util/lib/oae'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as TenantsAPI from 'oae-tenants/lib/api'; +import * as SearchAPI from 'oae-search'; +import * as SearchUtil from 'oae-search/lib/util'; const REGEX_SEARCH_ENDPOINT = /\/api\/search\/([^/]+)(\/.*)?/; diff --git a/packages/oae-search/lib/searches/deleted.js b/packages/oae-search/lib/searches/deleted.js index 09efd72d3c..cee836dd65 100644 --- a/packages/oae-search/lib/searches/deleted.js +++ b/packages/oae-search/lib/searches/deleted.js @@ -13,12 +13,12 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const OaeUtil = require('oae-util/lib/util'); +import * as OaeUtil from 'oae-util/lib/util'; -const { SearchConstants } = require('oae-search/lib/constants'); -const SearchUtil = require('oae-search/lib/util'); +import { SearchConstants } from 'oae-search/lib/constants'; +import * as SearchUtil from 'oae-search/lib/util'; /** * Search that searches through deleted resources @@ -34,7 +34,7 @@ const SearchUtil = require('oae-search/lib/util'); * @param {Object} callback.err An error that occurred, if any * @param {SearchResult} callback.results An object that represents the results of the query */ -module.exports = function(ctx, opts, callback) { +export default function(ctx, opts, callback) { // Sanitize custom search options opts = opts || {}; opts.limit = OaeUtil.getNumberParam(opts.limit, 10, 1, 25); @@ -53,10 +53,7 @@ module.exports = function(ctx, opts, callback) { // The query and filter objects for the Query DSL const query = SearchUtil.createQueryStringQuery(opts.q); - const filterResources = SearchUtil.filterResources( - opts.resourceTypes, - SearchConstants.deleted.ONLY - ); + const filterResources = SearchUtil.filterResources(opts.resourceTypes, SearchConstants.deleted.ONLY); // Apply the scope and access filters for the deleted search SearchUtil.filterScopeAndAccess(ctx, opts.scope, false, (err, filterScopeAndAccess) => { @@ -67,7 +64,7 @@ module.exports = function(ctx, opts, callback) { const filter = SearchUtil.filterAnd(filterResources, filterScopeAndAccess); return callback(null, SearchUtil.createQuery(query, filter, opts)); }); -}; +} /** * Resolve the scope for the search based on who is performing it and how they specified the scope @@ -82,5 +79,6 @@ const _resolveScope = function(ctx, scope) { if (user.isGlobalAdmin()) { return scope || SearchConstants.general.SCOPE_ALL; } + return user.tenant.alias; }; diff --git a/packages/oae-search/lib/searches/email.js b/packages/oae-search/lib/searches/email.js index e87c176b22..a4883b5a40 100644 --- a/packages/oae-search/lib/searches/email.js +++ b/packages/oae-search/lib/searches/email.js @@ -13,13 +13,13 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const OaeUtil = require('oae-util/lib/util'); +import * as OaeUtil from 'oae-util/lib/util'; +import * as SearchUtil from 'oae-search/lib/util'; +import * as TenantsAPI from 'oae-tenants/lib/api'; -const SearchUtil = require('oae-search/lib/util'); -const TenantsAPI = require('oae-tenants/lib/api'); -const { Validator } = require('oae-util/lib/validator'); +import { Validator } from 'oae-util/lib/validator'; /** * A search that searches on an exact "email" match, scoping its results by the specified scope and @@ -36,18 +36,14 @@ const { Validator } = require('oae-util/lib/validator'); * @param {Object} callback.err An error that occurred, if any * @param {SearchResult} callback.results An object that represents the results of the query */ -module.exports.queryBuilder = function(ctx, opts, callback) { +const queryBuilder = function(ctx, opts, callback) { // Sanitize custom search options opts = opts || {}; opts.limit = OaeUtil.getNumberParam(opts.limit, 10, 1, 25); const validator = new Validator(); - validator - .check(null, { code: 401, msg: 'Only authenticated users can use email search' }) - .isLoggedInUser(ctx); - validator - .check(opts.q, { code: 400, msg: 'An invalid email address has been specified' }) - .isEmail(); + validator.check(null, { code: 401, msg: 'Only authenticated users can use email search' }).isLoggedInUser(ctx); + validator.check(opts.q, { code: 400, msg: 'An invalid email address has been specified' }).isEmail(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -82,7 +78,9 @@ module.exports.queryBuilder = function(ctx, opts, callback) { * @param {Object} callback.err An error that occurred, if any * @param {Object} callback.results An object that represents the results of the query */ -module.exports.postProcessor = function(ctx, opts, results, callback) { +const postProcessor = function(ctx, opts, results, callback) { results.tenant = TenantsAPI.getTenantByEmail(opts.q); return callback(null, results); }; + +export { queryBuilder, postProcessor }; diff --git a/packages/oae-search/lib/searches/general.js b/packages/oae-search/lib/searches/general.js index bd569a0af2..a3ca083727 100644 --- a/packages/oae-search/lib/searches/general.js +++ b/packages/oae-search/lib/searches/general.js @@ -14,15 +14,15 @@ */ /* eslint-disable camelcase */ -const _ = require('underscore'); +import _ from 'underscore'; -const { ContentConstants } = require('oae-content/lib/constants'); -const { DiscussionsConstants } = require('oae-discussions/lib/constants'); -const { FoldersConstants } = require('oae-folders/lib/constants'); -const OaeUtil = require('oae-util/lib/util'); +import { ContentConstants } from 'oae-content/lib/constants'; +import { DiscussionsConstants } from 'oae-discussions/lib/constants'; +import { FoldersConstants } from 'oae-folders/lib/constants'; +import { SearchConstants } from 'oae-search/lib/constants'; -const { SearchConstants } = require('oae-search/lib/constants'); -const SearchUtil = require('oae-search/lib/util'); +import * as OaeUtil from 'oae-util/lib/util'; +import * as SearchUtil from 'oae-search/lib/util'; const RESOURCE_TYPES_ACCESS_SCOPED = [ SearchConstants.general.RESOURCE_TYPE_ALL, @@ -47,7 +47,7 @@ const RESOURCE_TYPES_ACCESS_SCOPED = [ * @param {Object} callback.err An error that occurred, if any * @param {SearchResult} callback.results An object that represents the results of the query */ -module.exports = function(ctx, opts, callback) { +export default function(ctx, opts, callback) { // Sanitize custom search options opts = opts || {}; opts.limit = OaeUtil.getNumberParam(opts.limit, 10, 1, 25); @@ -57,7 +57,7 @@ module.exports = function(ctx, opts, callback) { opts.searchAllResourceTypes = _.isEmpty(opts.resourceTypes); return _search(ctx, opts, callback); -}; +} /** * Perform the search that searches a 'q' analyzed field on documents, scoping it by user access. This is delegated from the diff --git a/packages/oae-search/lib/test/util.js b/packages/oae-search/lib/test/util.js index 26d6ce527f..39dea14223 100644 --- a/packages/oae-search/lib/test/util.js +++ b/packages/oae-search/lib/test/util.js @@ -13,15 +13,15 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const MqTestsUtil = require('oae-util/lib/test/mq-util'); -const RestAPI = require('oae-rest'); +import * as MqTestsUtil from 'oae-util/lib/test/mq-util'; +import * as RestAPI from 'oae-rest'; -const SearchAPI = require('oae-search'); -const ElasticSearch = require('oae-search/lib/internal/elasticsearch'); -const { SearchConstants } = require('oae-search/lib/constants'); +import * as SearchAPI from 'oae-search'; +import * as ElasticSearch from 'oae-search/lib/internal/elasticsearch'; +import { SearchConstants } from 'oae-search/lib/constants'; /** * Completely empty out the search index @@ -109,14 +109,7 @@ const assertSearchContains = function(restCtx, searchType, params, opts, contain * @param {Object} callback.response All search documents that were found with the search * @throws {AssertionError} Thrown if the search fails or any ids are found in the results */ -const assertSearchNotContains = function( - restCtx, - searchType, - params, - opts, - notContainIds, - callback -) { +const assertSearchNotContains = function(restCtx, searchType, params, opts, notContainIds, callback) { searchAll(restCtx, searchType, params, opts, (err, response) => { assert.ok(!err); assert.ok( @@ -191,6 +184,7 @@ const searchAll = function(restCtx, searchType, params, opts, callback) { if (err) { return callback(err); } + if (result.total === 0) { // We got 0 documents, just return the result as-is return callback(null, result); @@ -207,6 +201,7 @@ const searchAll = function(restCtx, searchType, params, opts, callback) { if (err) { return callback(err); } + if (_.isEmpty(data.results)) { // There are no more new results coming back which means we've got them all return callback(null, allData); @@ -257,7 +252,7 @@ const searchRefreshed = function(restCtx, searchType, params, opts, callback) { }); }; -module.exports = { +export { deleteAll, reindexAll, assertSearchSucceeds, diff --git a/packages/oae-search/lib/util.js b/packages/oae-search/lib/util.js index e65c8e9c9b..e8676db5d2 100644 --- a/packages/oae-search/lib/util.js +++ b/packages/oae-search/lib/util.js @@ -14,19 +14,22 @@ */ /* eslint-disable camelcase */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const AuthzAPI = require('oae-authz'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const log = require('oae-logger').logger('oae-search-util'); -const OaeUtil = require('oae-util/lib/util'); -const TenantsAPI = require('oae-tenants'); -const TenantsUtil = require('oae-tenants/lib/util'); -const { Validator } = require('oae-util/lib/validator'); +import { logger } from 'oae-logger'; -const { SearchConstants } = require('oae-search/lib/constants'); -const SearchModel = require('oae-search/lib/model'); +import * as AuthzAPI from 'oae-authz'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as TenantsAPI from 'oae-tenants'; +import * as TenantsUtil from 'oae-tenants/lib/util'; +import * as SearchModel from 'oae-search/lib/model'; + +import { SearchConstants } from 'oae-search/lib/constants'; +import { Validator } from 'oae-util/lib/validator'; +import { AuthzConstants } from 'oae-authz/lib/constants'; + +const log = logger('oae-search-util'); /** * Get the standard search parameters from the given request. @@ -74,10 +77,12 @@ const getScopeParam = function(val, defaultVal) { // If it is a valid scope type, return the scope as-is return val; } + if (TenantsAPI.getTenant(val)) { // If it is a valid tenant alias, return the scope as-is return val; } + // Otherwise, we default to limiting to the current tenant return defaultVal; }; @@ -95,6 +100,7 @@ const getSortDirParam = function(val, defaultVal, sortBy) { if (sortBy === SearchConstants.sort.field.SCORE || sortBy === SearchConstants.sort.field.MODIFIED) { return SearchConstants.sort.direction.DESC; } + defaultVal = defaultVal ? getSortDirParam(defaultVal) : SearchConstants.sort.direction.ASC; return _.contains(SearchConstants.sort.direction.OPTIONS, val) ? val : defaultVal; }; @@ -165,6 +171,7 @@ const transformSearchResults = function(ctx, transformers, results, callback) { if (!docsByType[type]) { docsByType[type] = {}; } + docsByType[type][id] = doc; docIdOrdering[id] = i; } @@ -373,6 +380,7 @@ const filterOr = function(...args) { if (_.isEmpty(filters)) { return null; } + if (filters.length === 1) { // If there is only one filter, no need to wrap it in an `or` return filters[0]; @@ -392,6 +400,7 @@ const filterAnd = function(...args) { if (_.isEmpty(filters)) { return null; } + if (filters.length === 1) { // If there is only one filter, no need to wrap it in an `and` return filters[0]; @@ -410,6 +419,7 @@ const filterNot = function(filter) { if (!filter) { return null; } + return { not: filter }; }; @@ -506,6 +516,7 @@ const filterInteractingTenants = function(tenantAlias) { // A private tenant can only interact with itself return filterTerm('tenantAlias', tenantAlias); } + // A public tenant can only interact with public tenants const nonInteractingTenantAliases = _.pluck(TenantsAPI.getNonInteractingTenants(), 'alias'); return filterNot(filterTerms('tenantAlias', nonInteractingTenantAliases)); @@ -538,10 +549,12 @@ const filterScopeAndAccess = function(ctx, scope, needsFilterByExplicitAccess, c if (err) { return callback(err); } + if (user && user.isGlobalAdmin() && scope === SearchConstants.general.SCOPE_ALL) { // Global admins can search all public resources, including private tenants' return callback(null, filterOr(implicitAccessFilter, explicitAccessFilter)); } + if (scope === SearchConstants.general.SCOPE_NETWORK || scope === SearchConstants.general.SCOPE_ALL) { // When searching network, we care about access and the scope of the tenant network (i.e., // scope public tenants away from private). All resources outside the network that the @@ -551,6 +564,7 @@ const filterScopeAndAccess = function(ctx, scope, needsFilterByExplicitAccess, c filterOr(filterAnd(implicitAccessFilter, interactingTenantAliasesFilter), explicitAccessFilter) ); } + if (scope === SearchConstants.general.SCOPE_INTERACT) { if (!user) { // Anonymous users cannot interact with anything, give an authorization error for this scenario @@ -579,6 +593,7 @@ const filterScopeAndAccess = function(ctx, scope, needsFilterByExplicitAccess, c ) ); } + if (scope === SearchConstants.general.SCOPE_MY) { if (!user) { // Anonymous users cannot interact with anything, give an authorization error for this scenario @@ -675,6 +690,7 @@ const filterExplicitAccess = function(ctx, callback) { // Anonymous users cannot have explicit access to anything return callback(); } + if (user.isGlobalAdmin()) { // The global admin has implicit access to everything, so explicit access is unnecessary return callback(); @@ -731,8 +747,10 @@ const filterCreatedBy = function(ctx, createdBy) { if (user.id === createdBy) { return filterTerm('createdBy', createdBy); } + return filterNot(filterTerm('createdBy', user.id)); } + return null; }; @@ -878,7 +896,7 @@ const getChildSearchDocumentId = function(type, resourceId, childId) { return util.format('%s#%s#%s', resourceId, type, childId || ''); }; -module.exports = { +export { getSearchParams, getQueryParam, getScopeParam, diff --git a/packages/oae-search/tests/test-elasticsearch.js b/packages/oae-search/tests/test-elasticsearch.js index ea39bcdc78..5661b3d0b3 100644 --- a/packages/oae-search/tests/test-elasticsearch.js +++ b/packages/oae-search/tests/test-elasticsearch.js @@ -13,122 +13,120 @@ * permissions and limitations under the License. */ -var assert = require('assert'); +import assert from 'assert'; -var TestsUtil = require('oae-tests/lib/util'); - -var ElasticSearch = require('oae-search/lib/internal/elasticsearch'); +import * as TestsUtil from 'oae-tests/lib/util'; +import * as ElasticSearch from 'oae-search/lib/internal/elasticsearch'; describe('ElasticSearch', function() { + /** + * Test that verifies the ability to create, verify (check "exists") and delete an ElasticSearch index + */ + it('verify create, verify and delete index', function(callback) { + const indexName = TestsUtil.generateTestElasticSearchName('oaetest-create-verify-delete'); + ElasticSearch.indexExists(indexName, function(err, exists) { + assert.ok(!err); + assert.ok(!exists); + + ElasticSearch.createIndex(indexName, {}, function(err) { + assert.ok(!err); - /** - * Test that verifies the ability to create, verify (check "exists") and delete an ElasticSearch index - */ - it('verify create, verify and delete index', function(callback) { - var indexName = TestsUtil.generateTestElasticSearchName('oaetest-create-verify-delete'); ElasticSearch.indexExists(indexName, function(err, exists) { - assert.ok(!err); - assert.ok(!exists); - - ElasticSearch.createIndex(indexName, {}, function(err) { - assert.ok(!err); - - ElasticSearch.indexExists(indexName, function(err, exists) { - assert.ok(!err); - assert.ok(exists); + assert.ok(!err); + assert.ok(exists); - ElasticSearch.deleteIndex(indexName, function(err) { - assert.ok(!err); + ElasticSearch.deleteIndex(indexName, function(err) { + assert.ok(!err); - ElasticSearch.indexExists(indexName, function(err, exists) { - assert.ok(!err); - assert.ok(!exists); - return callback(); - }); - }); - }); + ElasticSearch.indexExists(indexName, function(err, exists) { + assert.ok(!err); + assert.ok(!exists); + return callback(); }); + }); }); + }); }); + }); - /** - * Test that verifies there is no error when trying to create an index that already exists. It should just leave it alone - */ - it('verify no error creating existing index', function(callback) { - var indexName = TestsUtil.generateTestElasticSearchName('oaetest-create-nonerror-existing'); - ElasticSearch.indexExists(indexName, function(err, exists) { - assert.ok(!err); - assert.ok(!exists); + /** + * Test that verifies there is no error when trying to create an index that already exists. It should just leave it alone + */ + it('verify no error creating existing index', function(callback) { + const indexName = TestsUtil.generateTestElasticSearchName('oaetest-create-nonerror-existing'); + ElasticSearch.indexExists(indexName, function(err, exists) { + assert.ok(!err); + assert.ok(!exists); - ElasticSearch.createIndex(indexName, {}, function(err) { - assert.ok(!err); + ElasticSearch.createIndex(indexName, {}, function(err) { + assert.ok(!err); - ElasticSearch.createIndex(indexName, {}, function(err) { - assert.ok(!err); + ElasticSearch.createIndex(indexName, {}, function(err) { + assert.ok(!err); - ElasticSearch.deleteIndex(indexName, function(err) { - assert.ok(!err); - return callback(); - }); - }); - }); - }); - }); - - /** - * Test that verifies there is no error when trying to delete a non-existing index - */ - it('verify no error deleting non-existing index', function(callback) { - var indexName = TestsUtil.generateTestElasticSearchName('oaetest-delete-nonerror-existing'); - ElasticSearch.indexExists(indexName, function(err, exists) { + ElasticSearch.deleteIndex(indexName, function(err) { assert.ok(!err); - assert.ok(!exists); - - ElasticSearch.deleteIndex(indexName, function(err) { - assert.ok(!err); - return callback(); - }); + return callback(); + }); }); + }); + }); + }); + + /** + * Test that verifies there is no error when trying to delete a non-existing index + */ + it('verify no error deleting non-existing index', function(callback) { + const indexName = TestsUtil.generateTestElasticSearchName('oaetest-delete-nonerror-existing'); + ElasticSearch.indexExists(indexName, function(err, exists) { + assert.ok(!err); + assert.ok(!exists); + + ElasticSearch.deleteIndex(indexName, function(err) { + assert.ok(!err); + return callback(); + }); }); + }); - /** - * Test that verifies the ability to create and verify the existence of resource mappings - */ - it('verify put, verify mappings', function(callback) { - var typeName = TestsUtil.generateTestElasticSearchName('oaetest-put-verify-mappings'); - ElasticSearch.mappingExists(typeName, function(err, exists) { - assert.ok(!err); - assert.ok(!exists); + /** + * Test that verifies the ability to create and verify the existence of resource mappings + */ + it('verify put, verify mappings', function(callback) { + const typeName = TestsUtil.generateTestElasticSearchName('oaetest-put-verify-mappings'); + ElasticSearch.mappingExists(typeName, function(err, exists) { + assert.ok(!err); + assert.ok(!exists); - ElasticSearch.putMapping(typeName, {'testField': {'type': 'string'}}, null, function(err) { - assert.ok(!err); + ElasticSearch.putMapping(typeName, { testField: { type: 'string' } }, null, function(err) { + assert.ok(!err); - ElasticSearch.mappingExists(typeName, function(err, exists) { - assert.ok(!err); - assert.ok(exists); - return callback(); - }); - }); + ElasticSearch.mappingExists(typeName, function(err, exists) { + assert.ok(!err); + assert.ok(exists); + return callback(); }); + }); }); - - /** - * Test that verifies no error occurrs when trying to create a resource mapping by a name that already exists - */ - it('verify no error creating existing mapping', function(callback) { - var typeName = TestsUtil.generateTestElasticSearchName('oaetest-error-creating-existing'); - ElasticSearch.mappingExists(typeName, function(err, exists) { - assert.ok(!err); - assert.ok(!exists); - - ElasticSearch.putMapping(typeName, {'testField': {'type': 'string'}}, null, function(err) { - assert.ok(!err); - - ElasticSearch.putMapping(typeName, {'testField': {'type': 'string'}}, null, function(err) { - assert.ok(!err); - return callback(); - }); - }); + }); + + /** + * Test that verifies no error occurrs when trying to create a resource mapping by a name that already exists + */ + it('verify no error creating existing mapping', function(callback) { + const typeName = TestsUtil.generateTestElasticSearchName('oaetest-error-creating-existing'); + ElasticSearch.mappingExists(typeName, function(err, exists) { + assert.ok(!err); + assert.ok(!exists); + + ElasticSearch.putMapping(typeName, { testField: { type: 'string' } }, null, function(err) { + assert.ok(!err); + + ElasticSearch.putMapping(typeName, { testField: { type: 'string' } }, null, function(err) { + assert.ok(!err); + return callback(); }); + }); }); + }); }); diff --git a/packages/oae-search/tests/test-email-search.js b/packages/oae-search/tests/test-email-search.js index 3513b0be03..2cd8cf968e 100644 --- a/packages/oae-search/tests/test-email-search.js +++ b/packages/oae-search/tests/test-email-search.js @@ -13,16 +13,14 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const FoldersTestUtil = require('oae-folders/lib/test/util'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); - -const SearchTestUtil = require('oae-search/lib/test/util'); +import * as FoldersTestUtil from 'oae-folders/lib/test/util'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as SearchTestUtil from 'oae-search/lib/test/util'; describe('Email Search', () => { // Rest context that can be used every time we need to make a request as an anonymous user @@ -71,36 +69,20 @@ describe('Email Search', () => { } const test = tests.pop(); - const fn = test.visible - ? SearchTestUtil.assertSearchContains - : SearchTestUtil.assertSearchNotContains; + const fn = test.visible ? SearchTestUtil.assertSearchContains : SearchTestUtil.assertSearchNotContains; const expectedResultCount = test.visible ? 1 : 0; fn(test.restCtx, 'email', null, { q: test.target.email }, [test.target.id], response => { assert.strictEqual(response.results.length, expectedResultCount); // Also search in all uppers and all lowers to verify search case insensitivity - fn( - test.restCtx, - 'email', - null, - { q: test.target.email.toUpperCase() }, - [test.target.id], - response => { + fn(test.restCtx, 'email', null, { q: test.target.email.toUpperCase() }, [test.target.id], response => { + assert.strictEqual(response.results.length, expectedResultCount); + fn(test.restCtx, 'email', null, { q: test.target.email.toLowerCase() }, [test.target.id], response => { assert.strictEqual(response.results.length, expectedResultCount); - fn( - test.restCtx, - 'email', - null, - { q: test.target.email.toLowerCase() }, - [test.target.id], - response => { - assert.strictEqual(response.results.length, expectedResultCount); - return _runEmailSearchInteractionTests(tests, callback); - } - ); - } - ); + return _runEmailSearchInteractionTests(tests, callback); + }); + }); }); }; @@ -112,71 +94,31 @@ describe('Email Search', () => { assert.ok(!err); // Only authenticated users can search by email - SearchTestUtil.assertSearchFails( - anonymousRestContext, - 'email', - null, - { q: user.user.email }, - 401, - () => { - SearchTestUtil.assertSearchEquals( - user.restContext, - 'email', - null, - { q: user.user.email }, - [user.user.id], - () => { - SearchTestUtil.assertSearchEquals( - camAdminRestContext, - 'email', - null, - { q: user.user.email }, - [user.user.id], - () => { - SearchTestUtil.assertSearchEquals( - globalAdminOnTenantRestContext, - 'email', - null, - { q: user.user.email }, - [user.user.id], - () => { - return callback(); - } - ); - } - ); - } - ); - } - ); - }); - }); - - /** - * Test that verifies email search validates input properly - */ - it('verify validation of email search', callback => { - TestsUtil.generateTestUsers(camAdminRestContext, 1, (err, users, user) => { - assert.ok(!err); - - // Missing and invalid email should fail - SearchTestUtil.assertSearchFails(user.restContext, 'email', null, null, 400, () => { - SearchTestUtil.assertSearchFails( + SearchTestUtil.assertSearchFails(anonymousRestContext, 'email', null, { q: user.user.email }, 401, () => { + SearchTestUtil.assertSearchEquals( user.restContext, 'email', null, - { q: 'notanemail' }, - 400, + { q: user.user.email }, + [user.user.id], () => { - // Sanity check user can perform an email search SearchTestUtil.assertSearchEquals( - user.restContext, + camAdminRestContext, 'email', null, { q: user.user.email }, [user.user.id], () => { - return callback(); + SearchTestUtil.assertSearchEquals( + globalAdminOnTenantRestContext, + 'email', + null, + { q: user.user.email }, + [user.user.id], + () => { + return callback(); + } + ); } ); } @@ -185,272 +127,287 @@ describe('Email Search', () => { }); }); + /** + * Test that verifies email search validates input properly + */ + it('verify validation of email search', callback => { + TestsUtil.generateTestUsers(camAdminRestContext, 1, (err, users, user) => { + assert.ok(!err); + + // Missing and invalid email should fail + SearchTestUtil.assertSearchFails(user.restContext, 'email', null, null, 400, () => { + SearchTestUtil.assertSearchFails(user.restContext, 'email', null, { q: 'notanemail' }, 400, () => { + // Sanity check user can perform an email search + SearchTestUtil.assertSearchEquals( + user.restContext, + 'email', + null, + { q: user.user.email }, + [user.user.id], + () => { + return callback(); + } + ); + }); + }); + }); + }); + /** * Test that verifies that exact email search bypasses user profile visibility boundaries, but * does not violate tenant privacy boundaries */ it('verify email search does not violate tenant interaction boundaries', callback => { - TestsUtil.setupMultiTenantPrivacyEntities( - (publicTenant0, publicTenant1, privateTenant0, privateTenant1) => { - _runEmailSearchInteractionTests( - [ - // Public tenant user interaction with same-tenant users - { - restCtx: publicTenant0.publicUser.restContext, - target: publicTenant0.publicUser.user, - visible: true - }, - { - restCtx: publicTenant0.publicUser.restContext, - target: publicTenant0.loggedinUser.user, - visible: true - }, - { - restCtx: publicTenant0.publicUser.restContext, - target: publicTenant0.privateUser.user, - visible: true - }, + TestsUtil.setupMultiTenantPrivacyEntities((publicTenant0, publicTenant1, privateTenant0, privateTenant1) => { + _runEmailSearchInteractionTests( + [ + // Public tenant user interaction with same-tenant users + { + restCtx: publicTenant0.publicUser.restContext, + target: publicTenant0.publicUser.user, + visible: true + }, + { + restCtx: publicTenant0.publicUser.restContext, + target: publicTenant0.loggedinUser.user, + visible: true + }, + { + restCtx: publicTenant0.publicUser.restContext, + target: publicTenant0.privateUser.user, + visible: true + }, - // Public tenant user interaction with external public tenant users - { - restCtx: publicTenant0.publicUser.restContext, - target: publicTenant1.publicUser.user, - visible: true - }, - { - restCtx: publicTenant0.publicUser.restContext, - target: publicTenant1.loggedinUser.user, - visible: true - }, - { - restCtx: publicTenant0.publicUser.restContext, - target: publicTenant1.privateUser.user, - visible: true - }, + // Public tenant user interaction with external public tenant users + { + restCtx: publicTenant0.publicUser.restContext, + target: publicTenant1.publicUser.user, + visible: true + }, + { + restCtx: publicTenant0.publicUser.restContext, + target: publicTenant1.loggedinUser.user, + visible: true + }, + { + restCtx: publicTenant0.publicUser.restContext, + target: publicTenant1.privateUser.user, + visible: true + }, - // Public tenant user interaction with external private tenant users - { - restCtx: publicTenant0.publicUser.restContext, - target: privateTenant0.publicUser.user, - visible: false - }, - { - restCtx: publicTenant0.publicUser.restContext, - target: privateTenant0.loggedinUser.user, - visible: false - }, - { - restCtx: publicTenant0.publicUser.restContext, - target: privateTenant0.privateUser.user, - visible: false - }, + // Public tenant user interaction with external private tenant users + { + restCtx: publicTenant0.publicUser.restContext, + target: privateTenant0.publicUser.user, + visible: false + }, + { + restCtx: publicTenant0.publicUser.restContext, + target: privateTenant0.loggedinUser.user, + visible: false + }, + { + restCtx: publicTenant0.publicUser.restContext, + target: privateTenant0.privateUser.user, + visible: false + }, - // Private tenant user interaction with same-tenant users - { - restCtx: privateTenant0.publicUser.restContext, - target: privateTenant0.publicUser.user, - visible: true - }, - { - restCtx: privateTenant0.publicUser.restContext, - target: privateTenant0.loggedinUser.user, - visible: true - }, - { - restCtx: privateTenant0.publicUser.restContext, - target: privateTenant0.privateUser.user, - visible: true - }, + // Private tenant user interaction with same-tenant users + { + restCtx: privateTenant0.publicUser.restContext, + target: privateTenant0.publicUser.user, + visible: true + }, + { + restCtx: privateTenant0.publicUser.restContext, + target: privateTenant0.loggedinUser.user, + visible: true + }, + { + restCtx: privateTenant0.publicUser.restContext, + target: privateTenant0.privateUser.user, + visible: true + }, - // Private tenant user interaction with external public tenant users - { - restCtx: privateTenant0.publicUser.restContext, - target: publicTenant0.publicUser.user, - visible: false - }, - { - restCtx: privateTenant0.publicUser.restContext, - target: publicTenant0.loggedinUser.user, - visible: false - }, - { - restCtx: privateTenant0.publicUser.restContext, - target: publicTenant0.privateUser.user, - visible: false - }, + // Private tenant user interaction with external public tenant users + { + restCtx: privateTenant0.publicUser.restContext, + target: publicTenant0.publicUser.user, + visible: false + }, + { + restCtx: privateTenant0.publicUser.restContext, + target: publicTenant0.loggedinUser.user, + visible: false + }, + { + restCtx: privateTenant0.publicUser.restContext, + target: publicTenant0.privateUser.user, + visible: false + }, - // Private tenant user interaction with external private tenant users - { - restCtx: privateTenant0.publicUser.restContext, - target: privateTenant1.publicUser.user, - visible: false - }, - { - restCtx: privateTenant0.publicUser.restContext, - target: privateTenant1.loggedinUser.user, - visible: false - }, - { - restCtx: privateTenant0.publicUser.restContext, - target: privateTenant1.privateUser.user, - visible: false - } - ], - callback - ); - } - ); + // Private tenant user interaction with external private tenant users + { + restCtx: privateTenant0.publicUser.restContext, + target: privateTenant1.publicUser.user, + visible: false + }, + { + restCtx: privateTenant0.publicUser.restContext, + target: privateTenant1.loggedinUser.user, + visible: false + }, + { + restCtx: privateTenant0.publicUser.restContext, + target: privateTenant1.privateUser.user, + visible: false + } + ], + callback + ); + }); }); /** * Test that verifies interact scope will return all users that match a given email address */ it('verify email search returns all users that match a particular email', callback => { - TestsUtil.generateTestUsers( - camAdminRestContext, - 3, - (err, userInfos, userInfo0, userInfo1, userInfo2) => { - assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 3, (err, userInfos, userInfo0, userInfo1, userInfo2) => { + assert.ok(!err); - const allUserIds = _.chain([userInfo0, userInfo1, userInfo2]) - .pluck('user') - .pluck('id') - .value(); - const updateUserIds = _.chain([userInfo1, userInfo2]) - .pluck('user') - .pluck('id') - .value(); - PrincipalsTestUtil.assertUpdateUsersSucceeds( - camAdminRestContext, - updateUserIds, - { email: userInfo0.user.email }, - (users, tokens) => { - const userInfoTokens = _.map(users, user => { - return { - token: tokens[user.id], - userInfo: { - restContext: userInfos[user.id].restContext, - user: users[user.id] - } - }; - }); + const allUserIds = _.chain([userInfo0, userInfo1, userInfo2]) + .pluck('user') + .pluck('id') + .value(); + const updateUserIds = _.chain([userInfo1, userInfo2]) + .pluck('user') + .pluck('id') + .value(); + PrincipalsTestUtil.assertUpdateUsersSucceeds( + camAdminRestContext, + updateUserIds, + { email: userInfo0.user.email }, + (users, tokens) => { + const userInfoTokens = _.map(users, user => { + return { + token: tokens[user.id], + userInfo: { + restContext: userInfos[user.id].restContext, + user: users[user.id] + } + }; + }); - PrincipalsTestUtil.assertVerifyEmailsSucceeds(userInfoTokens, () => { - // Create one of each resource with the email address as the display name so - // we can ensure they don't come out of the search - RestAPI.Content.createLink( - userInfo0.restContext, - userInfo0.user.email, - userInfo0.user.email, - 'public', - 'google.com', - [], - [], - [], - (err, link) => { - assert.ok(!err); - RestAPI.Group.createGroup( - userInfo0.restContext, - userInfo0.user.email, - userInfo0.user.email, - 'public', - 'yes', - null, - null, - (err, group) => { - assert.ok(!err); - RestAPI.Discussions.createDiscussion( - userInfo0.restContext, - userInfo0.user.email, - userInfo0.user.email, - 'public', - null, - null, - (err, discussion) => { - assert.ok(!err); - FoldersTestUtil.assertCreateFolderSucceeds( - userInfo0.restContext, - userInfo0.user.email, - userInfo0.user.email, - 'public', - null, - null, - folder => { - SearchTestUtil.whenIndexingComplete(() => { - // Sanity check that the resources we just created can be searched with the email - const allResourceIds = _.pluck( - [link, group, discussion, folder], - 'id' - ); - SearchTestUtil.assertSearchContains( - userInfo0.restContext, - 'general', - null, - { q: userInfo0.user.email }, - allResourceIds, - () => { - // Now ensure that only the users come out of the email search - SearchTestUtil.assertSearchEquals( - userInfo0.restContext, - 'email', - null, - { q: userInfo0.user.email }, - allUserIds, - () => { - return callback(); - } - ); - } - ); - }); - } - ); - } - ); - } - ); - } - ); - }); - } - ); - } - ); + PrincipalsTestUtil.assertVerifyEmailsSucceeds(userInfoTokens, () => { + // Create one of each resource with the email address as the display name so + // we can ensure they don't come out of the search + RestAPI.Content.createLink( + userInfo0.restContext, + userInfo0.user.email, + userInfo0.user.email, + 'public', + 'google.com', + [], + [], + [], + (err, link) => { + assert.ok(!err); + RestAPI.Group.createGroup( + userInfo0.restContext, + userInfo0.user.email, + userInfo0.user.email, + 'public', + 'yes', + null, + null, + (err, group) => { + assert.ok(!err); + RestAPI.Discussions.createDiscussion( + userInfo0.restContext, + userInfo0.user.email, + userInfo0.user.email, + 'public', + null, + null, + (err, discussion) => { + assert.ok(!err); + FoldersTestUtil.assertCreateFolderSucceeds( + userInfo0.restContext, + userInfo0.user.email, + userInfo0.user.email, + 'public', + null, + null, + folder => { + SearchTestUtil.whenIndexingComplete(() => { + // Sanity check that the resources we just created can be searched with the email + const allResourceIds = _.pluck([link, group, discussion, folder], 'id'); + SearchTestUtil.assertSearchContains( + userInfo0.restContext, + 'general', + null, + { q: userInfo0.user.email }, + allResourceIds, + () => { + // Now ensure that only the users come out of the email search + SearchTestUtil.assertSearchEquals( + userInfo0.restContext, + 'email', + null, + { q: userInfo0.user.email }, + allUserIds, + () => { + return callback(); + } + ); + } + ); + }); + } + ); + } + ); + } + ); + } + ); + }); + } + ); + }); }); /** * Test that verifies that the email search endpoint returns the tenant that matches the email domain */ it('verify email search returns the tenant that matches the email domain', callback => { - TestsUtil.setupMultiTenantPrivacyEntities( - (publicTenant0, publicTenant1, privateTenant0, privateTenant1) => { - SearchTestUtil.whenIndexingComplete(() => { - SearchTestUtil.assertSearchSucceeds( - publicTenant0.publicUser.restContext, - 'email', - null, - { q: privateTenant1.publicUser.user.email }, - data => { - assert.ok(_.isObject(data.tenant)); - assert.ok(!data.tenant.isGuestTenant); - assert.strictEqual(data.tenant.alias, privateTenant1.tenant.alias); + TestsUtil.setupMultiTenantPrivacyEntities((publicTenant0, publicTenant1, privateTenant0, privateTenant1) => { + SearchTestUtil.whenIndexingComplete(() => { + SearchTestUtil.assertSearchSucceeds( + publicTenant0.publicUser.restContext, + 'email', + null, + { q: privateTenant1.publicUser.user.email }, + data => { + assert.ok(_.isObject(data.tenant)); + assert.ok(!data.tenant.isGuestTenant); + assert.strictEqual(data.tenant.alias, privateTenant1.tenant.alias); - // Search for an email address that would end up on the guest tenant - SearchTestUtil.assertSearchSucceeds( - publicTenant0.publicUser.restContext, - 'email', - null, - { q: 'an.email@ends.up.on.the.guest.tenant.com' }, - data => { - assert.ok(_.isObject(data.tenant)); - assert.ok(data.tenant.isGuestTenant); - return callback(); - } - ); - } - ); - }); - } - ); + // Search for an email address that would end up on the guest tenant + SearchTestUtil.assertSearchSucceeds( + publicTenant0.publicUser.restContext, + 'email', + null, + { q: 'an.email@ends.up.on.the.guest.tenant.com' }, + data => { + assert.ok(_.isObject(data.tenant)); + assert.ok(data.tenant.isGuestTenant); + return callback(); + } + ); + } + ); + }); + }); }); }); diff --git a/packages/oae-search/tests/test-general-search.js b/packages/oae-search/tests/test-general-search.js index 3f8a04d71f..31fc761a27 100644 --- a/packages/oae-search/tests/test-general-search.js +++ b/packages/oae-search/tests/test-general-search.js @@ -14,18 +14,18 @@ */ /* eslint-disable radix */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const ConfigTestUtil = require('oae-config/lib/test/util'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const TenantsTestUtil = require('oae-tenants/lib/test/util'); -const TestsUtil = require('oae-tests'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; +import * as TestsUtil from 'oae-tests'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; -const SearchTestsUtil = require('oae-search/lib/test/util'); +import { RestContext } from 'oae-rest/lib/model'; describe('General Search', () => { // Rest context that can be used every time we need to make a request as an anonymous user @@ -870,14 +870,14 @@ describe('General Search', () => { */ it('verify a variety of valid and invalid values for the resourceTypes parameter', callback => { /*! - * Helper function that verifies that a search result feed has (or doesn't have) results of certain resourceTypes - * - * @param {SearchResult} results The search results object - * @param {Boolean} shouldHaveUser Whether or not the results should contain a user object - * @param {Boolean} shouldHaveGroup Whether or not the results should contain a group object - * @param {Boolean} shouldHaveContent Whether or not the results should contain a content object - * @return {Object} The search document. `null` if it didn't exist - */ + * Helper function that verifies that a search result feed has (or doesn't have) results of certain resourceTypes + * + * @param {SearchResult} results The search results object + * @param {Boolean} shouldHaveUser Whether or not the results should contain a user object + * @param {Boolean} shouldHaveGroup Whether or not the results should contain a group object + * @param {Boolean} shouldHaveContent Whether or not the results should contain a content object + * @return {Object} The search document. `null` if it didn't exist + */ const _verifyHasResourceTypes = function(results, shouldHaveUser, shouldHaveGroup, shouldHaveContent) { let hasUser = false; let hasGroup = false; @@ -4234,6 +4234,7 @@ describe('General Search', () => { return i; } } + return -1; }; diff --git a/packages/oae-search/tests/test-search-api.js b/packages/oae-search/tests/test-search-api.js index 59f997fcf7..c25d06f8df 100644 --- a/packages/oae-search/tests/test-search-api.js +++ b/packages/oae-search/tests/test-search-api.js @@ -14,16 +14,17 @@ */ /* eslint-disable camelcase */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const MQTestsUtil = require('oae-util/lib/test/mq-util'); -const RestAPI = require('oae-rest'); -const SearchAPI = require('oae-search'); -const { SearchConstants } = require('oae-search/lib/constants'); -const TaskQueue = require('oae-util/lib/taskqueue'); -const TestsUtil = require('oae-tests/lib/util'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as MQTestsUtil from 'oae-util/lib/test/mq-util'; +import * as RestAPI from 'oae-rest'; +import * as SearchAPI from 'oae-search'; +import * as TaskQueue from 'oae-util/lib/taskqueue'; +import * as TestsUtil from 'oae-tests/lib/util'; + +import { SearchConstants } from 'oae-search/lib/constants'; describe('Search API', () => { // REST Contexts we will use to execute requests @@ -41,10 +42,10 @@ describe('Search API', () => { assert.ok(!err); /*! - * Task handler that will just drain the queue. - * - * @see TaskQueue#bind - */ + * Task handler that will just drain the queue. + * + * @see TaskQueue#bind + */ const _handleTaskDrain = function(data, mqCallback) { // Simply callback, which acknowledges messages without doing anything. mqCallback(); @@ -66,10 +67,7 @@ describe('Search API', () => { // Try and register a second transformer of the same type, log the error and verify it happened. assert.throws(() => { - SearchAPI.registerSearchDocumentTransformer( - 'test-registerSearchDocumentTransformer', - () => {} - ); + SearchAPI.registerSearchDocumentTransformer('test-registerSearchDocumentTransformer', () => {}); }, Error); return callback(); @@ -166,11 +164,11 @@ describe('Search API', () => { assert.ok(!err); /*! - * Simply call the test callback to continue tests. If this is not invoked, the test will timeout - * and fail. - * - * @see TaskQueue#bind - */ + * Simply call the test callback to continue tests. If this is not invoked, the test will timeout + * and fail. + * + * @see TaskQueue#bind + */ const _handleTask = function(data, mqCallback) { mqCallback(); callback(); @@ -197,10 +195,10 @@ describe('Search API', () => { assert.ok(!err); /*! - * Task handler that will fail the test if invoked. - * - * @see TaskQueue#bind - */ + * Task handler that will fail the test if invoked. + * + * @see TaskQueue#bind + */ const _handleTaskFail = function(data, mqCallback) { mqCallback(); assert.fail('Did not expect the task to be invoked.'); @@ -287,29 +285,19 @@ describe('Search API', () => { assert.ok(!err); // Send an index task of a document of the proper resource type - SearchAPI.postIndexTask( - 'test_resource_type', - [{ id: 't:cam:test' }], - { children: true }, - err => { - assert.ok(!err); - - // Send an index task of a document of not the proper resource type - SearchAPI.postIndexTask( - 'not_test_resource_type', - [{ id: 'n:cam:test' }], - { children: true }, - err => { - // Wait for the producers to be invoked - MQTestsUtil.whenTasksEmpty(SearchConstants.mq.TASK_INDEX_DOCUMENT, () => { - // Ensure only the proper resource type invoked the producer - assert.strictEqual(invoked, 1); - return callback(); - }); - } - ); - } - ); + SearchAPI.postIndexTask('test_resource_type', [{ id: 't:cam:test' }], { children: true }, err => { + assert.ok(!err); + + // Send an index task of a document of not the proper resource type + SearchAPI.postIndexTask('not_test_resource_type', [{ id: 'n:cam:test' }], { children: true }, err => { + // Wait for the producers to be invoked + MQTestsUtil.whenTasksEmpty(SearchConstants.mq.TASK_INDEX_DOCUMENT, () => { + // Ensure only the proper resource type invoked the producer + assert.strictEqual(invoked, 1); + return callback(); + }); + }); + }); } ); }); @@ -339,29 +327,19 @@ describe('Search API', () => { assert.ok(!err); // Send an index task of a document of the proper resource type - SearchAPI.postIndexTask( - 'test_resource_type', - [{ id: 't:cam:test' }], - { children: true }, - err => { - assert.ok(!err); - - // Send an index task of a document of not the proper resource type - SearchAPI.postIndexTask( - 'another_test_resource_type', - [{ id: 'n:cam:test' }], - { children: true }, - err => { - // Wait for the producers to be invoked - MQTestsUtil.whenTasksEmpty(SearchConstants.mq.TASK_INDEX_DOCUMENT, () => { - // Ensure only the proper resource type invoked the producer - assert.strictEqual(invoked, 2); - return callback(); - }); - } - ); - } - ); + SearchAPI.postIndexTask('test_resource_type', [{ id: 't:cam:test' }], { children: true }, err => { + assert.ok(!err); + + // Send an index task of a document of not the proper resource type + SearchAPI.postIndexTask('another_test_resource_type', [{ id: 'n:cam:test' }], { children: true }, err => { + // Wait for the producers to be invoked + MQTestsUtil.whenTasksEmpty(SearchConstants.mq.TASK_INDEX_DOCUMENT, () => { + // Ensure only the proper resource type invoked the producer + assert.strictEqual(invoked, 2); + return callback(); + }); + }); + }); } ); }); diff --git a/packages/oae-search/tests/test-search-util.js b/packages/oae-search/tests/test-search-util.js index d84d6c80bb..a0e3d468b5 100644 --- a/packages/oae-search/tests/test-search-util.js +++ b/packages/oae-search/tests/test-search-util.js @@ -13,14 +13,14 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const { Context } = require('oae-context'); -const TestsUtil = require('oae-tests/lib/util'); +import { Context } from 'oae-context'; +import { SearchConstants } from 'oae-search/lib/constants'; -const { SearchConstants } = require('oae-search/lib/constants'); -const SearchUtil = require('oae-search/lib/util'); +import * as TestsUtil from 'oae-tests/lib/util'; +import * as SearchUtil from 'oae-search/lib/util'; describe('Search Util', () => { describe('#getSearchParams', () => { @@ -300,18 +300,12 @@ describe('Search Util', () => { assert.strictEqual(filterOneResource.and[1].terms.resourceType[0], 'content'); assert.strictEqual(filterOneResource.and[2].not.exists.field, 'deleted'); - const filterAllDeletedResources = SearchUtil.filterResources( - null, - SearchConstants.deleted.ONLY - ); + const filterAllDeletedResources = SearchUtil.filterResources(null, SearchConstants.deleted.ONLY); assert.strictEqual(filterAllDeletedResources.and.length, 2); assert.strictEqual(filterAllDeletedResources.and[0].term._type, 'resource'); assert.strictEqual(filterAllDeletedResources.and[1].exists.field, 'deleted'); - const filterAllDeletedAndExistingResources = SearchUtil.filterResources( - null, - SearchConstants.deleted.BOTH - ); + const filterAllDeletedAndExistingResources = SearchUtil.filterResources(null, SearchConstants.deleted.BOTH); assert.strictEqual(filterAllDeletedAndExistingResources.term._type, 'resource'); return callback(); @@ -398,16 +392,10 @@ describe('Search Util', () => { assert.strictEqual(SearchUtil.getSortDirParam(validType, validType2), validType); assert.strictEqual(SearchUtil.getSortDirParam(validType), validType); assert.strictEqual(SearchUtil.getSortDirParam('not-valid', validType), validType); - assert.strictEqual( - SearchUtil.getSortDirParam('not-valid', 'not-valid'), - SearchConstants.sort.direction.ASC - ); + assert.strictEqual(SearchUtil.getSortDirParam('not-valid', 'not-valid'), SearchConstants.sort.direction.ASC); assert.strictEqual(SearchUtil.getSortDirParam(validType), validType); assert.strictEqual(SearchUtil.getSortDirParam(null, validType), validType); - assert.strictEqual( - SearchUtil.getSortDirParam(null, null), - SearchConstants.sort.direction.ASC - ); + assert.strictEqual(SearchUtil.getSortDirParam(null, null), SearchConstants.sort.direction.ASC); assert.strictEqual(SearchUtil.getSortDirParam(validType), validType); assert.strictEqual(SearchUtil.getSortDirParam(undefined, validType), validType); assert.strictEqual(SearchUtil.getSortDirParam(), SearchConstants.sort.direction.ASC); @@ -422,16 +410,10 @@ describe('Search Util', () => { const tenantAlias = global.oaeTests.tenants.cam.alias; assert.strictEqual(SearchUtil.getScopeParam(), SearchConstants.general.SCOPE_ALL); assert.strictEqual(SearchUtil.getScopeParam('invalid'), SearchConstants.general.SCOPE_ALL); - assert.strictEqual( - SearchUtil.getScopeParam('invalid', 'invalid'), - SearchConstants.general.SCOPE_ALL - ); + assert.strictEqual(SearchUtil.getScopeParam('invalid', 'invalid'), SearchConstants.general.SCOPE_ALL); assert.strictEqual(SearchUtil.getScopeParam('invalid', tenantAlias), tenantAlias); assert.strictEqual(SearchUtil.getScopeParam(tenantAlias), tenantAlias); - assert.strictEqual( - SearchUtil.getScopeParam(tenantAlias, SearchConstants.general.SCOPE_ALL), - tenantAlias - ); + assert.strictEqual(SearchUtil.getScopeParam(tenantAlias, SearchConstants.general.SCOPE_ALL), tenantAlias); assert.strictEqual( SearchUtil.getScopeParam(SearchConstants.general.SCOPE_ALL, tenantAlias), SearchConstants.general.SCOPE_ALL diff --git a/packages/oae-search/tests/test-tenants-search.js b/packages/oae-search/tests/test-tenants-search.js index c8044d470c..602a66adff 100644 --- a/packages/oae-search/tests/test-tenants-search.js +++ b/packages/oae-search/tests/test-tenants-search.js @@ -13,13 +13,12 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const TenantsTestUtil = require('oae-tenants/lib/test/util'); -const TestsUtil = require('oae-tests'); - -const SearchTestsUtil = require('oae-search/lib/test/util'); +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; +import * as TestsUtil from 'oae-tests'; +import * as SearchTestsUtil from 'oae-search/lib/test/util'; describe('Tenants Search', () => { // Rest context that can be used every time we need to make a request as an anonymous user diff --git a/packages/oae-telemetry/lib/api.js b/packages/oae-telemetry/lib/api.js index 6822cf0fda..64488a2613 100644 --- a/packages/oae-telemetry/lib/api.js +++ b/packages/oae-telemetry/lib/api.js @@ -13,14 +13,16 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const EmitterAPI = require('oae-emitter'); -const Locking = require('oae-util/lib/locking'); -const log = require('oae-logger').logger('oae-telemetry'); -const OaeUtil = require('oae-util/lib/util'); -const Redis = require('oae-util/lib/redis'); +import * as EmitterAPI from 'oae-emitter'; +import * as Locking from 'oae-util/lib/locking'; +import { logger } from 'oae-logger'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as Redis from 'oae-util/lib/redis'; + +const log = logger('oae-telemetry'); let telemetryConfig = null; @@ -39,6 +41,7 @@ let resetIntervalId = null; * * `publish(data)` - Indicates that data was just published to a telemetry publisher. The data that was published is provided in the event */ const TelemetryAPI = new EmitterAPI.EventEmitter(); +const emitter = TelemetryAPI; /** * Initializes the Telemetry API so that it can start accepting and publishing metrics to an @@ -67,10 +70,7 @@ const init = function(_telemetryConfig, callback) { } // Begin the publish and reset intervals - publishIntervalId = setInterval( - _publishTelemetryData, - telemetryConfig.publishInterval * 1000 - ); + publishIntervalId = setInterval(_publishTelemetryData, telemetryConfig.publishInterval * 1000); resetIntervalId = setInterval(_resetTelemetryCounts, telemetryConfig.resetInterval * 1000); return callback(); @@ -274,6 +274,7 @@ const _resetTelemetryCounts = function(callback) { log().error({ err }, 'Error acquiring lock to reset telemetry data'); return callback(); } + if (!token) { // We didn't acquire the lock, so don't bother resetting return callback(); @@ -338,31 +339,28 @@ const _pushCountsToRedis = function(callback) { */ const _lockAndGetCounts = function(callback) { // Try and fetch the lock for the duration of the publishing interval - Locking.acquire( - _getTelemetryCountPublishLock(), - telemetryConfig.publishInterval, - (err, token) => { + Locking.acquire(_getTelemetryCountPublishLock(), telemetryConfig.publishInterval, (err, token) => { + if (err) { + log().error({ err }, 'Error acquiring lock to publish telemetry counts'); + return callback(); + } + + if (!token) { + // We didn't acquire the lock, so don't bother with the counts + return callback(); + } + + // Fetch the full counts hash in redis + _getCounts((err, countsHash) => { if (err) { - log().error({ err }, 'Error acquiring lock to publish telemetry counts'); - return callback(); - } - if (!token) { - // We didn't acquire the lock, so don't bother with the counts return callback(); } - // Fetch the full counts hash in redis - _getCounts((err, countsHash) => { - if (err) { - return callback(); - } - - // We return without releasing the lock, because the expiry of the lock managers the collection interval, so that if another application - // server tries to collect 1 second after this, they will fail to get the lock - return callback(countsHash); - }); - } - ); + // We return without releasing the lock, because the expiry of the lock managers the collection interval, so that if another application + // server tries to collect 1 second after this, they will fail to get the lock + return callback(countsHash); + }); + }); }; /** @@ -529,11 +527,4 @@ const _getTelemetryCountKeyParts = function(telemetryCountKey) { }; }; -module.exports = { - emitter: TelemetryAPI, - init, - telemetry, - request, - reset, - getTelemetryData -}; +export { emitter, init, telemetry, request, reset, getTelemetryData }; diff --git a/packages/oae-telemetry/lib/init.js b/packages/oae-telemetry/lib/init.js index 896e8b052c..24ee27e500 100644 --- a/packages/oae-telemetry/lib/init.js +++ b/packages/oae-telemetry/lib/init.js @@ -13,11 +13,11 @@ * permissions and limitations under the License. */ -const TelemetryAPI = require('oae-telemetry'); +import * as TelemetryAPI from 'oae-telemetry'; /** * Initializes the TelemetryAPI so it can begin publishing metric data. */ -module.exports = function(config, callback) { +export function init(config, callback) { TelemetryAPI.init(config.telemetry, callback); -}; +} diff --git a/packages/oae-telemetry/lib/publishers/circonus.js b/packages/oae-telemetry/lib/publishers/circonus.js index ef7305bc4a..4f1e80d490 100644 --- a/packages/oae-telemetry/lib/publishers/circonus.js +++ b/packages/oae-telemetry/lib/publishers/circonus.js @@ -13,10 +13,11 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const request = require('request'); +import _ from 'underscore'; +import request from 'request'; +import { logger } from 'oae-logger'; -const log = require('oae-logger').logger('telemetry-circonus'); +const log = logger('telemetry-circonus'); let circonusConfig = null; @@ -70,18 +71,13 @@ const publish = function(data) { if (err) { return log().warn({ err }, 'Error publishing telemetry data to circonus'); } + if (response.statusCode !== 200) { - return log().warn( - { body, code: response.statusCode }, - 'Circonus replied with a non-200 response' - ); + return log().warn({ body, code: response.statusCode }, 'Circonus replied with a non-200 response'); } return log().info('Sent %d metrics to circonus', metricsToSend); }); }; -module.exports = { - init, - publish -}; +export { init, publish }; diff --git a/packages/oae-telemetry/lib/publishers/console.js b/packages/oae-telemetry/lib/publishers/console.js index ec81abcaf4..5487b3c46c 100644 --- a/packages/oae-telemetry/lib/publishers/console.js +++ b/packages/oae-telemetry/lib/publishers/console.js @@ -13,9 +13,11 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const log = require('oae-logger').logger('telemetry-console'); +import { logger } from 'oae-logger'; + +const log = logger('telemetry-console'); /** * Starts monitoring redis and logs the telemetry data on the console. @@ -53,7 +55,8 @@ const _padString = function(str, char, length) { while (str.length < length) { str += char; } + return str; }; -module.exports = { publish, init }; +export { publish, init }; diff --git a/packages/oae-telemetry/lib/rest.js b/packages/oae-telemetry/lib/rest.js index b15d723306..fa0708e892 100644 --- a/packages/oae-telemetry/lib/rest.js +++ b/packages/oae-telemetry/lib/rest.js @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -var OAE = require('oae-util/lib/oae'); -var TelemetryAPI = require('./api'); +import * as OAE from 'oae-util/lib/oae'; +import * as TelemetryAPI from './api'; /** * @REST getTelemetry @@ -29,14 +29,15 @@ var TelemetryAPI = require('./api'); * @HttpResponse 401 Only global administrators are allowed to retrieve telemetry data */ OAE.globalAdminRouter.on('get', '/api/telemetry', function(req, res) { - if (req.ctx.user() && req.ctx.user().isGlobalAdmin()) { - TelemetryAPI.getTelemetryData(function(err, data) { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(200).send(data); - }); - } else { - return res.status(401).send('Only global administrators are allowed to retrieve telemetry data'); - } + if (req.ctx.user() && req.ctx.user().isGlobalAdmin()) { + TelemetryAPI.getTelemetryData(function(err, data) { + if (err) { + return res.status(err.code).send(err.msg); + } + + res.status(200).send(data); + }); + } else { + return res.status(401).send('Only global administrators are allowed to retrieve telemetry data'); + } }); diff --git a/packages/oae-telemetry/tests/test-telemetry.js b/packages/oae-telemetry/tests/test-telemetry.js index 2dc11d3be3..5fe154a85e 100644 --- a/packages/oae-telemetry/tests/test-telemetry.js +++ b/packages/oae-telemetry/tests/test-telemetry.js @@ -13,19 +13,19 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const RestAPI = require('oae-rest'); -const TelemetryAPI = require('oae-telemetry'); -const TestsUtil = require('oae-tests'); +import * as RestAPI from 'oae-rest'; +import * as TelemetryAPI from 'oae-telemetry'; +import * as TestsUtil from 'oae-tests'; describe('Telemetry', () => { /*! - * Create an enabled telemetry configuration from the given configuration - * - * @param {Object} config The configuration object with which to create an enabled telemetry config - */ + * Create an enabled telemetry configuration from the given configuration + * + * @param {Object} config The configuration object with which to create an enabled telemetry config + */ const _createConfig = function(config) { return _.extend({ enabled: true }, config); }; @@ -178,10 +178,7 @@ describe('Telemetry', () => { RestAPI.Telemetry.getTelemetryData(anonymousGlobalRestContext, (err, res) => { assert.ok(err); assert.strictEqual(err.code, 401); - assert.strictEqual( - err.msg, - 'Only global administrators are allowed to retrieve telemetry data' - ); + assert.strictEqual(err.msg, 'Only global administrators are allowed to retrieve telemetry data'); // Request the telemetry data using a tenant user RestAPI.Telemetry.getTelemetryData(john.restContext, (err, res) => { diff --git a/packages/oae-tenants/config/config.js b/packages/oae-tenants/config/config.js index d75c2b5383..bd27ed221d 100644 --- a/packages/oae-tenants/config/config.js +++ b/packages/oae-tenants/config/config.js @@ -13,11 +13,11 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const Fields = require('oae-config/lib/fields'); -const TZ = require('oae-util/lib/tz'); +import * as Fields from 'oae-config/lib/fields'; +import * as TZ from 'oae-util/lib/tz'; // Get an object that we can pass in the config List field as the set of options that should be // presented to the user. We add in the offset in the displayname of each element @@ -43,8 +43,7 @@ const timezoneConfigValues = _.chain(TZ.getZones()) const sign = timezone.offset > 0 ? '-' : '+'; const offsetHoursStr = offsetHours < 10 ? '0' + offsetHours.toString() : offsetHours.toString(); - const offsetMinutesStr = - offsetMinutes < 10 ? '0' + offsetMinutes.toString() : offsetMinutes.toString(); + const offsetMinutesStr = offsetMinutes < 10 ? '0' + offsetMinutes.toString() : offsetMinutes.toString(); let offsetLabel = 'GMT'; if (timezone.offset !== 0) { @@ -58,7 +57,7 @@ const timezoneConfigValues = _.chain(TZ.getZones()) }) .value(); -module.exports = { +const result = { title: 'OAE Tenant Module', instance: { name: 'Instance Information', @@ -88,12 +87,10 @@ module.exports = { name: 'Tenant Admin Action', description: 'Actions a tenant admin is allowed to do', elements: { - allowStop: new Fields.Bool( - 'Stop tenant', - 'Allow a tenant admin to stop the tenant server', - false, - { tenantOverride: false, suppress: true } - ) + allowStop: new Fields.Bool('Stop tenant', 'Allow a tenant admin to stop the tenant server', false, { + tenantOverride: false, + suppress: true + }) } }, tenantprivacy: { @@ -110,13 +107,9 @@ module.exports = { name: 'Tenant Timezone', description: 'Specifies the tenant timezone', elements: { - timezone: new Fields.List( - 'Tenant Timezone', - 'Tenant timezone', - 'Etc/UTC', - timezoneConfigValues, - { suppress: true } - ) + timezone: new Fields.List('Tenant Timezone', 'Tenant timezone', 'Etc/UTC', timezoneConfigValues, { + suppress: true + }) } }, guests: { @@ -129,3 +122,4 @@ module.exports = { } } }; +export const { title, instance, actions, tenantprivacy, timezone, guests } = result; diff --git a/packages/oae-tenants/config/tenant.js b/packages/oae-tenants/config/tenant.js index 3dc4e9d77a..9a6f6130a2 100644 --- a/packages/oae-tenants/config/tenant.js +++ b/packages/oae-tenants/config/tenant.js @@ -13,33 +13,34 @@ * permissions and limitations under the License. */ -var Fields = require('oae-config/lib/fields'); +/* eslint-disable-file camelcase */ +import * as Fields from 'oae-config/lib/fields'; -var widths = [ - { - 'name': '25%', - 'value': '3' - }, - { - 'name': '33%', - 'value': '4' - }, - { - 'name': '50%', - 'value': '6' - }, - { - 'name': '66%', - 'value': '8' - }, - { - 'name': '75%', - 'value': '9' - }, - { - 'name': '100%', - 'value': '12' - } +const widths = [ + { + name: '25%', + value: '3' + }, + { + name: '33%', + value: '4' + }, + { + name: '50%', + value: '6' + }, + { + name: '66%', + value: '8' + }, + { + name: '75%', + value: '9' + }, + { + name: '100%', + value: '12' + } ]; /** @@ -65,164 +66,196 @@ var widths = [ * @return {Object} The created landing page block * @api private */ -var _createBlock = function(opts) { - opts = opts || {}; +const _createBlock = function(opts) { + opts = opts || {}; - var type = opts.type || 'empty'; - var horizontalAlign = opts.horizontalAlign || 'center'; - var verticalAlign = opts.verticalAlign || 'middle'; - var xs = opts.xs || '12'; - var sm = opts.sm || '12'; - var md = opts.md || '12'; - var lg = opts.lg || '12'; + const type = opts.type || 'empty'; + const horizontalAlign = opts.horizontalAlign || 'center'; + const verticalAlign = opts.verticalAlign || 'middle'; + const xs = opts.xs || '12'; + const sm = opts.sm || '12'; + const md = opts.md || '12'; + const lg = opts.lg || '12'; - return { - 'name': 'Block values', - 'description': 'Block values', - 'elements': { - 'type': new Fields.List('Block type', 'Block type', type, [ - { - 'name': 'Empty', - 'value': 'empty' - }, - { - 'name': 'Search', - 'value': 'search' - }, - { - 'name': 'Text', - 'value': 'text' - }, - { - 'name': 'Text with icon', - 'value': 'iconText' - }, - { - 'name': 'Image', - 'value': 'image' - }, - { - 'name': 'Video', - 'value': 'video' - } - ], {'suppress': true}), - 'xs': new Fields.List('XS Block width', 'Block width at extra small resolution', xs, widths, {'suppress': true}), - 'sm': new Fields.List('SM Block width', 'Block width at small resolution', sm, widths, {'suppress': true}), - 'md': new Fields.List('MD Block width', 'Block width at medium resolution', md, widths, {'suppress': true}), - 'lg': new Fields.List('LG Block width', 'Block width at large resolution', lg, widths, {'suppress': true}), - 'minHeight': new Fields.Text('Block minimum height', 'Minimum height for the block in px', opts.minHeight, {'suppress': true}), - 'horizontalAlign': new Fields.List('Horizontal alignment', 'Horizontal alignment', horizontalAlign, [ - { - 'name': 'Left', - 'value': 'left' - }, - { - 'name': 'Center', - 'value': 'center' - }, - { - 'name': 'Right', - 'value': 'right' - } - ], {'suppress': true}), - 'verticalAlign': new Fields.List('Vertical alignment', 'Vertical alignment', verticalAlign, [ - { - 'name': 'Top', - 'value': 'top' - }, - { - 'name': 'Middle', - 'value': 'middle' - }, - { - 'name': 'Bottom', - 'value': 'bottom' - } - ], {'suppress': true}), - 'bgColor': new Fields.Text('Block background color', 'Background color for the block', opts.bgColor, {'suppress': true}), - 'titleColor': new Fields.Text('Block title color', 'Title color for the block', opts.titleColor, {'suppress': true}), - 'textColor': new Fields.Text('Block text color', 'Text color for the block', opts.textColor, {'suppress': true}), - 'text': new Fields.InternationalizableText('Block text', 'Text content for the block', opts.text, {'suppress': true}), - 'icon': new Fields.Text('Block icon', 'Icon for the block', opts.icon, {'suppress': true}), - 'imgUrl': new Fields.Text('Image URL', 'Image URL', opts.imgUrl, {'suppress': true}), - 'videoUrl': new Fields.Text('Video URL', 'Video URL', opts.videoUrl, {'suppress': true}), - 'videoPlaceholder': new Fields.Text('Video Placeholder Image', 'URL for video placeholder image', opts.videoPlaceholder, {'suppress': true}) - } - }; + return { + name: 'Block values', + description: 'Block values', + elements: { + type: new Fields.List( + 'Block type', + 'Block type', + type, + [ + { + name: 'Empty', + value: 'empty' + }, + { + name: 'Search', + value: 'search' + }, + { + name: 'Text', + value: 'text' + }, + { + name: 'Text with icon', + value: 'iconText' + }, + { + name: 'Image', + value: 'image' + }, + { + name: 'Video', + value: 'video' + } + ], + { suppress: true } + ), + xs: new Fields.List('XS Block width', 'Block width at extra small resolution', xs, widths, { suppress: true }), + sm: new Fields.List('SM Block width', 'Block width at small resolution', sm, widths, { suppress: true }), + md: new Fields.List('MD Block width', 'Block width at medium resolution', md, widths, { suppress: true }), + lg: new Fields.List('LG Block width', 'Block width at large resolution', lg, widths, { suppress: true }), + minHeight: new Fields.Text('Block minimum height', 'Minimum height for the block in px', opts.minHeight, { + suppress: true + }), + horizontalAlign: new Fields.List( + 'Horizontal alignment', + 'Horizontal alignment', + horizontalAlign, + [ + { + name: 'Left', + value: 'left' + }, + { + name: 'Center', + value: 'center' + }, + { + name: 'Right', + value: 'right' + } + ], + { suppress: true } + ), + verticalAlign: new Fields.List( + 'Vertical alignment', + 'Vertical alignment', + verticalAlign, + [ + { + name: 'Top', + value: 'top' + }, + { + name: 'Middle', + value: 'middle' + }, + { + name: 'Bottom', + value: 'bottom' + } + ], + { suppress: true } + ), + bgColor: new Fields.Text('Block background color', 'Background color for the block', opts.bgColor, { + suppress: true + }), + titleColor: new Fields.Text('Block title color', 'Title color for the block', opts.titleColor, { + suppress: true + }), + textColor: new Fields.Text('Block text color', 'Text color for the block', opts.textColor, { suppress: true }), + text: new Fields.InternationalizableText('Block text', 'Text content for the block', opts.text, { + suppress: true + }), + icon: new Fields.Text('Block icon', 'Icon for the block', opts.icon, { suppress: true }), + imgUrl: new Fields.Text('Image URL', 'Image URL', opts.imgUrl, { suppress: true }), + videoUrl: new Fields.Text('Video URL', 'Video URL', opts.videoUrl, { suppress: true }), + videoPlaceholder: new Fields.Text( + 'Video Placeholder Image', + 'URL for video placeholder image', + opts.videoPlaceholder, + { suppress: true } + ) + } + }; }; -module.exports = { - 'title': 'OAE Tenant Module', - 'block_1': _createBlock({ - 'type': 'search', - 'xs': '12', - 'sm': '12', - 'md': '12', - 'lg': '12' - }), - 'block_2': _createBlock({ - 'type': 'video', - 'xs': '12', - 'sm': '12', - 'md': '8', - 'lg': '8', - 'minHeight': '290', - 'videoUrl': 'https://www.youtube.com/watch?v=cfiM87Y0pWw', - 'videoPlaceholder': '/ui/img/index-video-bg.png' - }), - 'block_3': _createBlock({ - 'type': 'text', - 'xs': '12', - 'sm': '6', - 'md': '4', - 'lg': '4', - 'titleColor': '#FFF', - 'textColor': '#FFF', - 'text': '# __MSG__SUPPORTING_ACADEMIC_COLLABORATION__\n __MSG__A_POWERFULL_NEW_WAY_FOR_STUDENTS_AND_FACULTY_TO_CREATE_KNOWLEDGE_COLLABORATE_AND_CONNECT_WITH_THE_WORLD__' - }), - 'block_4': _createBlock({ - 'type': 'iconText', - 'xs': '12', - 'sm': '6', - 'md': '4', - 'lg': '4', - 'verticalAlign': 'top', - 'bgColor': '#FFF', - 'titleColor': '#4199CA', - 'textColor': '#000', - 'text': '#### __MSG__AUTHORING_EXPERIENCE__\n __MSG__RICH_COMPELLING_INTERACTIVE_CONTENT_AUTHORING__', - 'icon': 'fa-edit' - }), - 'block_5': _createBlock({ - 'type': 'iconText', - 'xs': '12', - 'sm': '6', - 'md': '4', - 'lg': '4', - 'verticalAlign': 'top', - 'bgColor': '#424242', - 'titleColor': '#FFF', - 'textColor': '#FFF', - 'text': '#### __MSG__CHANNELS_OF_COMMUNICATION__\n __MSG__PARTICIPATING_IN_DISCUSSIONS_AND_FEEDBACK_WITHIN_PERSONALIZED_NETWORKS__', - 'icon': 'fa-comments' - }), - 'block_6': _createBlock({ - 'type': 'iconText', - 'xs': '12', - 'sm': '6', - 'md': '4', - 'lg': '4', - 'verticalAlign': 'top', - 'bgColor': '#f0EEEC', - 'titleColor': '#424242', - 'textColor': '#000', - 'text': '#### __MSG__ACCESS_TO_CONTENT__\n __MSG__EXPANDED_ACCESS_TO_LEARNING_AND_RESEARCH_MATERIALS_BETTER_CONNECTS_LIBRARY_SERVICES__', - 'icon': 'fa-cloud-download' - }), - 'block_7': _createBlock(), - 'block_8': _createBlock(), - 'block_9': _createBlock(), - 'block_10': _createBlock(), - 'block_11': _createBlock(), - 'block_12': _createBlock() -}; +export const title = 'OAE Tenant Module'; +export const block_1 = _createBlock({ + type: 'search', + xs: '12', + sm: '12', + md: '12', + lg: '12' +}); +export const block_2 = _createBlock({ + type: 'video', + xs: '12', + sm: '12', + md: '8', + lg: '8', + minHeight: '290', + videoUrl: 'https://www.youtube.com/watch?v=cfiM87Y0pWw', + videoPlaceholder: '/ui/img/index-video-bg.png' +}); +export const block_3 = _createBlock({ + type: 'text', + xs: '12', + sm: '6', + md: '4', + lg: '4', + titleColor: '#FFF', + textColor: '#FFF', + text: + '# __MSG__SUPPORTING_ACADEMIC_COLLABORATION__\n __MSG__A_POWERFULL_NEW_WAY_FOR_STUDENTS_AND_FACULTY_TO_CREATE_KNOWLEDGE_COLLABORATE_AND_CONNECT_WITH_THE_WORLD__' +}); +export const block_4 = _createBlock({ + type: 'iconText', + xs: '12', + sm: '6', + md: '4', + lg: '4', + verticalAlign: 'top', + bgColor: '#FFF', + titleColor: '#4199CA', + textColor: '#000', + text: '#### __MSG__AUTHORING_EXPERIENCE__\n __MSG__RICH_COMPELLING_INTERACTIVE_CONTENT_AUTHORING__', + icon: 'fa-edit' +}); +export const block_5 = _createBlock({ + type: 'iconText', + xs: '12', + sm: '6', + md: '4', + lg: '4', + verticalAlign: 'top', + bgColor: '#424242', + titleColor: '#FFF', + textColor: '#FFF', + text: + '#### __MSG__CHANNELS_OF_COMMUNICATION__\n __MSG__PARTICIPATING_IN_DISCUSSIONS_AND_FEEDBACK_WITHIN_PERSONALIZED_NETWORKS__', + icon: 'fa-comments' +}); +export const block_6 = _createBlock({ + type: 'iconText', + xs: '12', + sm: '6', + md: '4', + lg: '4', + verticalAlign: 'top', + bgColor: '#f0EEEC', + titleColor: '#424242', + textColor: '#000', + text: + '#### __MSG__ACCESS_TO_CONTENT__\n __MSG__EXPANDED_ACCESS_TO_LEARNING_AND_RESEARCH_MATERIALS_BETTER_CONNECTS_LIBRARY_SERVICES__', + icon: 'fa-cloud-download' +}); +export const block_7 = _createBlock(); +export const block_8 = _createBlock(); +export const block_9 = _createBlock(); +export const block_10 = _createBlock(); +export const block_11 = _createBlock(); +export const block_12 = _createBlock(); diff --git a/packages/oae-tenants/lib/api.js b/packages/oae-tenants/lib/api.js index 1fc2ec4510..8ab99d13b1 100644 --- a/packages/oae-tenants/lib/api.js +++ b/packages/oae-tenants/lib/api.js @@ -13,25 +13,35 @@ * permissions and limitations under the License. */ -const { logger } = require('oae-logger'); - -const util = require('util'); -const _ = require('underscore'); -const async = require('async'); - -const Cassandra = require('oae-util/lib/cassandra'); -const ConfigAPI = require('oae-config'); -const EmitterAPI = require('oae-emitter'); -const OAE = require('oae-util/lib/oae'); -const OaeUtil = require('oae-util/lib/util'); -const Pubsub = require('oae-util/lib/pubsub'); -const { Validator } = require('oae-util/lib/validator'); - -const { Tenant } = require('./model'); -const TenantEmailDomainIndex = require('./internal/emailDomainIndex'); -const TenantIndex = require('./internal/tenantIndex'); -const TenantNetworksDAO = require('./internal/dao.networks'); -const TenantsUtil = require('./util'); +import util from 'util'; +import { logger } from 'oae-logger'; + +import _ from 'underscore'; +import async from 'async'; + +// We have to require the config api inline, as this would +// otherwise lead to circular require calls +import { setUpConfig } from 'oae-config'; +// We have to require the UI api inline, as this would +// otherwise lead to circular require calls +import * as UIAPI from 'oae-ui'; +import * as UserAPI from 'oae-principals/lib/api.user.js'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as ConfigAPI from 'oae-config'; +import * as EmitterAPI from 'oae-emitter'; +import * as OAE from 'oae-util/lib/oae'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as Pubsub from 'oae-util/lib/pubsub'; +import { Validator } from 'oae-util/lib/validator'; +import TenantEmailDomainIndex from './internal/emailDomainIndex'; +import TenantIndex from './internal/tenantIndex'; +import * as TenantNetworksDAO from './internal/dao.networks'; +import * as TenantsUtil from './util'; + +import { Tenant } from './model'; + +const TenantsConfig = setUpConfig('oae-tenants'); + const log = logger('oae-tenants'); // Caches the server configuration as specified in the config.js file @@ -236,6 +246,7 @@ const searchTenants = function(q, opts) { if (result.isGlobalAdminServer) { return false; } + if (!includeDisabled) { return result.active && !result.deleted; } @@ -422,6 +433,7 @@ const _updateCachedTenant = function(tenantAlias, callback) { if (err) { return callback(err); } + if (_.isEmpty(rows)) { TenantsAPI.emit('cached'); return callback(); @@ -801,7 +813,6 @@ const disableTenants = function(ctx, aliases, disabled, callback) { // Broadcast the message accross the cluster so we can start/stop the tenants const cmd = disabled ? 'stop' : 'start'; - const UserAPI = require('oae-principals/lib/api.user.js'); async.mapSeries( aliases, (eachAlias, transformed) => { @@ -823,6 +834,7 @@ const disableTenants = function(ctx, aliases, disabled, callback) { if (err) { callback(err); } + return callback(); } ); @@ -852,6 +864,7 @@ const getLandingPage = function(ctx) { landingPage.push(block); } } + return landingPage; }; @@ -890,14 +903,11 @@ const _getLandingPageBlock = function(ctx, blockName) { block.text = block.text[locale] || block.text.default; } - // We have to require the UI api inline, as this would - // otherwise lead to circular require calls - const UIAPI = require('oae-ui'); - // If any URLs are configured, we try to resolve them in the hashed UI files if (block.imgUrl) { block.imgUrl = UIAPI.getHashedPath(block.imgUrl); } + if (block.videoPlaceholder) { block.videoPlaceholder = UIAPI.getHashedPath(block.videoPlaceholder); } @@ -915,10 +925,6 @@ const _getLandingPageBlock = function(ctx, blockName) { * @api private */ const _setLandingPageBlockAttribute = function(ctx, block, blockName, attributeName) { - // We have to require the config api inline, as this would - // otherwise lead to circular require calls - const TenantsConfig = require('oae-config').config('oae-tenants'); - // Set the attribute value block[attributeName] = TenantsConfig.getValue(ctx.tenant().alias, blockName, attributeName); }; @@ -1017,11 +1023,12 @@ const _tenantToStorageHash = function(tenant) { if (hash.emailDomains) { hash.emailDomains = hash.emailDomains.join(','); } + return hash; }; -module.exports = { - emitter: TenantsAPI, +export { + TenantsAPI as emitter, init, getTenants, getNonInteractingTenants, diff --git a/packages/oae-tenants/lib/api.networks.js b/packages/oae-tenants/lib/api.networks.js index 33b678af3d..1e4fee4833 100644 --- a/packages/oae-tenants/lib/api.networks.js +++ b/packages/oae-tenants/lib/api.networks.js @@ -13,13 +13,12 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const { Validator } = require('oae-util/lib/validator'); - -const TenantNetworksDAO = require('./internal/dao.networks'); -const TenantsAPI = require('./api'); +import { Validator } from 'oae-util/lib/validator'; +import * as TenantNetworksDAO from './internal/dao.networks'; +import * as TenantsAPI from './api'; /** * Create a tenant network @@ -38,9 +37,7 @@ const createTenantNetwork = function(ctx, displayName, callback) { msg: 'Must be a global administrator user to create a tenant network' }) .isGlobalAdministratorUser(ctx); - validator - .check(displayName, { code: 400, msg: 'A tenant network must contain a display name' }) - .notEmpty(); + validator.check(displayName, { code: 400, msg: 'A tenant network must contain a display name' }).notEmpty(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -108,9 +105,7 @@ const updateTenantNetwork = function(ctx, id, displayName, callback) { }) .isGlobalAdministratorUser(ctx); validator.check(id, { code: 400, msg: 'Must specify a tenant network id' }).notEmpty(); - validator - .check(displayName, { code: 400, msg: 'A tenant network must contain a display name' }) - .notEmpty(); + validator.check(displayName, { code: 400, msg: 'A tenant network must contain a display name' }).notEmpty(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -159,12 +154,8 @@ const addTenantAliases = function(ctx, tenantNetworkId, tenantAliases, callback) msg: 'Must be a global administrator user to update a tenant network' }) .isGlobalAdministratorUser(ctx); - validator - .check(tenantNetworkId, { code: 400, msg: 'Must specify a tenant network id' }) - .notEmpty(); - validator - .check(tenantAliases, { code: 400, msg: 'Must specify a list of tenant aliases to add' }) - .notNull(); + validator.check(tenantNetworkId, { code: 400, msg: 'Must specify a tenant network id' }).notEmpty(); + validator.check(tenantAliases, { code: 400, msg: 'Must specify a list of tenant aliases to add' }).notNull(); validator .check(tenantAliases.length, { code: 400, @@ -203,12 +194,8 @@ const removeTenantAliases = function(ctx, tenantNetworkId, tenantAliases, callba msg: 'Must be a global administrator user to update a tenant network' }) .isGlobalAdministratorUser(ctx); - validator - .check(tenantNetworkId, { code: 400, msg: 'Must specify a tenant network id' }) - .notEmpty(); - validator - .check(tenantAliases, { code: 400, msg: 'Must specify a list of tenant aliases to remove' }) - .notNull(); + validator.check(tenantNetworkId, { code: 400, msg: 'Must specify a tenant network id' }).notEmpty(); + validator.check(tenantAliases, { code: 400, msg: 'Must specify a list of tenant aliases to remove' }).notNull(); validator .check(tenantAliases.length, { code: 400, @@ -222,7 +209,7 @@ const removeTenantAliases = function(ctx, tenantNetworkId, tenantAliases, callba return TenantNetworksDAO.removeTenantAliases(tenantNetworkId, tenantAliases, callback); }; -module.exports = { +export { createTenantNetwork, getTenantNetworks, updateTenantNetwork, diff --git a/packages/oae-tenants/lib/init.js b/packages/oae-tenants/lib/init.js index bcd44cbc2c..dcb7a937d4 100644 --- a/packages/oae-tenants/lib/init.js +++ b/packages/oae-tenants/lib/init.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const TenantsAPI = require('oae-tenants'); +import * as TenantsAPI from 'oae-tenants'; -module.exports = function(config, callback) { +export function init(config, callback) { // Initialize the middleware that will add the tenant onto the request TenantsAPI.init(config.servers, callback); -}; +} diff --git a/packages/oae-tenants/lib/internal/dao.networks.js b/packages/oae-tenants/lib/internal/dao.networks.js index 776f3b92cc..78a489e755 100644 --- a/packages/oae-tenants/lib/internal/dao.networks.js +++ b/packages/oae-tenants/lib/internal/dao.networks.js @@ -13,17 +13,20 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); -const clone = require('clone'); -const ShortId = require('shortid'); +import util from 'util'; +import _ from 'underscore'; +import clone from 'clone'; +import ShortId from 'shortid'; -const log = require('oae-logger').logger('oae-tenants'); -const Cassandra = require('oae-util/lib/cassandra'); -const EmitterAPI = require('oae-emitter'); -const Pubsub = require('oae-util/lib/pubsub'); +import { logger } from 'oae-logger'; -const { TenantNetwork } = require('../model'); +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as EmitterAPI from 'oae-emitter'; +import * as Pubsub from 'oae-util/lib/pubsub'; + +import { TenantNetwork } from '../model'; + +const log = logger('oae-tenants'); // A cache that holds all tenant networks keyed by tenantNetworkId let _cacheTenantNetworks = null; @@ -129,6 +132,7 @@ const getTenantNetwork = function(id, callback) { if (err) { return callback(err); } + if (!_cacheTenantNetworks[id]) { return callback({ code: 404, @@ -227,6 +231,7 @@ const addTenantAliases = function(tenantNetworkId, tenantAliases, callback) { if (err) { return callback(err); } + if (_.isEmpty(tenantAliases)) { return callback(); } @@ -284,6 +289,7 @@ const removeTenantAliases = function(tenantNetworkId, tenantAliases, callback) { if (err) { return callback(err); } + if (_.isEmpty(tenantAliases)) { return callback(); } @@ -291,8 +297,7 @@ const removeTenantAliases = function(tenantNetworkId, tenantAliases, callback) { // Create and execute the delete queries const queries = _.map(tenantAliases, tenantAlias => { return { - query: - 'DELETE FROM "TenantNetworkTenants" WHERE "tenantNetworkId" = ? AND "tenantAlias" = ?', + query: 'DELETE FROM "TenantNetworkTenants" WHERE "tenantNetworkId" = ? AND "tenantAlias" = ?', parameters: [tenantNetworkId, tenantAlias] }; }); @@ -358,8 +363,7 @@ const _getAllTenantNetworkTenantAliasesFromCassandra = function(tenantNetworkIds _.chain(rows) .map(Cassandra.rowToHash) .each(rowHash => { - tenantNetworkAliases[rowHash.tenantNetworkId] = - tenantNetworkAliases[rowHash.tenantNetworkId] || []; + tenantNetworkAliases[rowHash.tenantNetworkId] = tenantNetworkAliases[rowHash.tenantNetworkId] || []; tenantNetworkAliases[rowHash.tenantNetworkId].push(rowHash.tenantAlias); }); @@ -403,31 +407,27 @@ const _ensureCache = function(callback) { } // Load all known tenant network tenant associations from Cassandra - _getAllTenantNetworkTenantAliasesFromCassandra( - _.keys(tenantNetworks), - (err, tenantNetworkTenantAliases) => { - if (err) { - return callback(err); - } - - // Reset the caches - _cacheTenantNetworks = tenantNetworks; - _cacheTenantAliasesByTenantNetworkId = tenantNetworkTenantAliases; - _cacheTenantNetworkIdsByTenantAlias = {}; - - // Build the inverted TenantAlias->TenantNetworkIds cache - _.each(_cacheTenantAliasesByTenantNetworkId, (tenantAliases, tenantNetworkId) => { - _.each(tenantAliases, tenantAlias => { - _cacheTenantNetworkIdsByTenantAlias[tenantAlias] = - _cacheTenantNetworkIdsByTenantAlias[tenantAlias] || []; - _cacheTenantNetworkIdsByTenantAlias[tenantAlias].push(tenantNetworkId); - }); + _getAllTenantNetworkTenantAliasesFromCassandra(_.keys(tenantNetworks), (err, tenantNetworkTenantAliases) => { + if (err) { + return callback(err); + } + + // Reset the caches + _cacheTenantNetworks = tenantNetworks; + _cacheTenantAliasesByTenantNetworkId = tenantNetworkTenantAliases; + _cacheTenantNetworkIdsByTenantAlias = {}; + + // Build the inverted TenantAlias->TenantNetworkIds cache + _.each(_cacheTenantAliasesByTenantNetworkId, (tenantAliases, tenantNetworkId) => { + _.each(tenantAliases, tenantAlias => { + _cacheTenantNetworkIdsByTenantAlias[tenantAlias] = _cacheTenantNetworkIdsByTenantAlias[tenantAlias] || []; + _cacheTenantNetworkIdsByTenantAlias[tenantAlias].push(tenantNetworkId); }); + }); - emitter.emit('revalidate'); - return callback(); - } - ); + emitter.emit('revalidate'); + return callback(); + }); }); }; @@ -454,7 +454,7 @@ const _invalidateLocalCache = function() { emitter.emit('invalidate'); }; -module.exports = { +export { emitter, init, createTenantNetwork, diff --git a/packages/oae-tenants/lib/internal/emailDomainIndex.js b/packages/oae-tenants/lib/internal/emailDomainIndex.js index 0a4aa24c7d..3a5838dbf1 100644 --- a/packages/oae-tenants/lib/internal/emailDomainIndex.js +++ b/packages/oae-tenants/lib/internal/emailDomainIndex.js @@ -14,7 +14,7 @@ */ /* eslint-disable unicorn/filename-case */ -const _ = require('underscore'); +import _ from 'underscore'; /** * Indexes domains so that suffix-matching can be efficiently performed. An entry can be add to @@ -43,12 +43,12 @@ const EmailDomainIndex = function() { const index = {}; /*! - * Find all string values that are set to domains that are descendants of the given email - * domain - * - * @param {String} emailDomain The email domain for which to find descendant values - * @return {String[]} The values (not a domain, the value that was set to the email domain) associated descendants of the specified domain - */ + * Find all string values that are set to domains that are descendants of the given email + * domain + * + * @param {String} emailDomain The email domain for which to find descendant values + * @return {String[]} The values (not a domain, the value that was set to the email domain) associated descendants of the specified domain + */ const _find = function(emailDomain) { if (!emailDomain) { return []; @@ -62,12 +62,12 @@ const EmailDomainIndex = function() { }; /*! - * Set the given string alias to the given email domain - * - * @param {String} alias The string alias to set - * @param {String} emailDomain The email domain to use as the key - * @return {String} If specified, indicates that there was a conflict. The value will be one of potentially many string values that the domain conflicted with. If `false`y, it indicates that setting the value was successful and there were no conflicts - */ + * Set the given string alias to the given email domain + * + * @param {String} alias The string alias to set + * @param {String} emailDomain The email domain to use as the key + * @return {String} If specified, indicates that there was a conflict. The value will be one of potentially many string values that the domain conflicted with. If `false`y, it indicates that setting the value was successful and there were no conflicts + */ const _set = function(alias, emailDomain) { if (!emailDomain) { return; @@ -98,6 +98,7 @@ const EmailDomainIndex = function() { // If we found a leaf node, we can't set anything as we'll overwrite an existing entry return segment; } + if (_.isString(segment[lastPart])) { // If this domain is an exact match to an existing domain, we cannot override, it should // be deleted first instead. Result in a conflict @@ -112,8 +113,8 @@ const EmailDomainIndex = function() { }; /*! - * @see EmailDomainIndex.match - */ + * @see EmailDomainIndex.match + */ const _match = function(emailDomain) { if (!emailDomain) { return null; @@ -126,6 +127,7 @@ const EmailDomainIndex = function() { // If the result is a tenant alias string, we have found a tenant return; } + if (!result) { // If we reached the end without finding a string leaf, we have exhausted the tree return; @@ -138,8 +140,8 @@ const EmailDomainIndex = function() { }; /*! - * @see EmailDomainIndex.conflict - */ + * @see EmailDomainIndex.conflict + */ const _conflict = function(alias, emailDomain) { // If there is an existing match for this email domain that is not this tenant alias, we // return with the alias that it conflicts with. We cannot proceed with the update @@ -160,10 +162,10 @@ const EmailDomainIndex = function() { }; /*! - * Delete a domain from the index - * - * @param {String} oldEmailDomain The email domain to remove from the index - */ + * Delete a domain from the index + * + * @param {String} oldEmailDomain The email domain to remove from the index + */ const _delete = function(oldEmailDomain) { if (!oldEmailDomain) { return; @@ -266,6 +268,7 @@ const _findStringLeaves = function(obj, _leaves) { // If we've reached the end of the search, return the aggregated `_leaves` return _leaves; } + if (_.isString(obj)) { // If we have arrived at a leaf, aggregate the leaf node and return the array _leaves.push(obj); @@ -308,4 +311,4 @@ const _split = function(emailDomain) { return emailDomain.split('.').reverse(); }; -module.exports = EmailDomainIndex; +export default EmailDomainIndex; diff --git a/packages/oae-tenants/lib/internal/tenantIndex.js b/packages/oae-tenants/lib/internal/tenantIndex.js index 0e34b0aaf9..99c51a9255 100644 --- a/packages/oae-tenants/lib/internal/tenantIndex.js +++ b/packages/oae-tenants/lib/internal/tenantIndex.js @@ -14,8 +14,8 @@ */ /* eslint-disable unicorn/filename-case */ -const _ = require('underscore'); -const lunr = require('lunr'); +import _ from 'underscore'; +import lunr from 'lunr'; /** * Represents an index where tenants can be indexed and then later full-text searched @@ -90,4 +90,4 @@ const _tenantToDocument = function(tenant) { return _.pick(tenant, 'alias', 'host', 'displayName'); }; -module.exports = TenantIndex; +export default TenantIndex; diff --git a/packages/oae-tenants/lib/migration.js b/packages/oae-tenants/lib/migration.js index 822f5a4f47..69f03411a1 100644 --- a/packages/oae-tenants/lib/migration.js +++ b/packages/oae-tenants/lib/migration.js @@ -1,4 +1,4 @@ -const Cassandra = require('oae-util/lib/cassandra'); +import { createColumnFamilies } from 'oae-util/lib/cassandra'; /** * Ensure that the tenant schema is created. If the tenant schema has not been created, or the default tenant has not been seeded, @@ -8,7 +8,7 @@ const Cassandra = require('oae-util/lib/cassandra'); * @api private */ const ensureSchema = function(callback) { - Cassandra.createColumnFamilies( + createColumnFamilies( { Tenant: 'CREATE TABLE "Tenant" ("alias" text PRIMARY KEY, "displayName" text, "host" text, "emailDomains" text, "countryCode" text, "active" boolean)', @@ -20,4 +20,4 @@ const ensureSchema = function(callback) { ); }; -module.exports = { ensureSchema }; +export { ensureSchema }; diff --git a/packages/oae-tenants/lib/model.js b/packages/oae-tenants/lib/model.js index 51c1e11ec2..1b81e36859 100644 --- a/packages/oae-tenants/lib/model.js +++ b/packages/oae-tenants/lib/model.js @@ -79,7 +79,4 @@ const TenantNetwork = function(id, displayName) { return that; }; -module.exports = { - Tenant, - TenantNetwork -}; +export { Tenant, TenantNetwork }; diff --git a/packages/oae-tenants/lib/rest.js b/packages/oae-tenants/lib/rest.js index b2581832b7..ba82dc17e9 100644 --- a/packages/oae-tenants/lib/rest.js +++ b/packages/oae-tenants/lib/rest.js @@ -13,17 +13,16 @@ * permissions and limitations under the License. */ -var _ = require('underscore'); -var OAE = require('oae-util/lib/oae'); -var OaeUtil = require('oae-util/lib/util'); +import _ from 'underscore'; -var TenantsAPI = require('oae-tenants/lib/api'); -var TenantNetworksAPI = require('oae-tenants/lib/api.networks'); +import * as OAE from 'oae-util/lib/oae'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as TenantsAPI from 'oae-tenants/lib/api'; +import * as TenantNetworksAPI from 'oae-tenants/lib/api.networks'; - -///////////////////// +/// ////////////////// // TENANT NETWORKS // -///////////////////// +/// ////////////////// /** * @REST getTenantNetworks @@ -38,13 +37,13 @@ var TenantNetworksAPI = require('oae-tenants/lib/api.networks'); * @HttpResponse 401 Must be a global administrator user to view tenant networks */ OAE.globalAdminRouter.on('get', '/api/tenantNetworks', function(req, res) { - TenantNetworksAPI.getTenantNetworks(req.ctx, function(err, tenantNetworks) { - if (err) { - return res.status(err.code).send(err.msg); - } + TenantNetworksAPI.getTenantNetworks(req.ctx, function(err, tenantNetworks) { + if (err) { + return res.status(err.code).send(err.msg); + } - return res.status(200).send(tenantNetworks); - }); + return res.status(200).send(tenantNetworks); + }); }); /** @@ -62,13 +61,13 @@ OAE.globalAdminRouter.on('get', '/api/tenantNetworks', function(req, res) { * @HttpResponse 401 Must be a global administrator user to create a tenant network */ OAE.globalAdminRouter.on('post', '/api/tenantNetwork/create', function(req, res) { - TenantNetworksAPI.createTenantNetwork(req.ctx, req.body.displayName, function(err, tenantNetwork) { - if (err) { - return res.status(err.code).send(err.msg); - } + TenantNetworksAPI.createTenantNetwork(req.ctx, req.body.displayName, function(err, tenantNetwork) { + if (err) { + return res.status(err.code).send(err.msg); + } - return res.status(201).send(tenantNetwork); - }); + return res.status(201).send(tenantNetwork); + }); }); /** @@ -88,13 +87,13 @@ OAE.globalAdminRouter.on('post', '/api/tenantNetwork/create', function(req, res) * @HttpResponse 401 Must be a global administrator user to update a tenant network */ OAE.globalAdminRouter.on('post', '/api/tenantNetwork/:id', function(req, res) { - TenantNetworksAPI.updateTenantNetwork(req.ctx, req.params.id, req.body.displayName, function(err, tenantNetwork) { - if (err) { - return res.status(err.code).send(err.msg); - } + TenantNetworksAPI.updateTenantNetwork(req.ctx, req.params.id, req.body.displayName, function(err, tenantNetwork) { + if (err) { + return res.status(err.code).send(err.msg); + } - return res.status(200).send(tenantNetwork); - }); + return res.status(200).send(tenantNetwork); + }); }); /** @@ -111,13 +110,13 @@ OAE.globalAdminRouter.on('post', '/api/tenantNetwork/:id', function(req, res) { * @HttpResponse 401 Must be a global administrator user to delete a tenant network */ OAE.globalAdminRouter.on('delete', '/api/tenantNetwork/:id', function(req, res) { - TenantNetworksAPI.deleteTenantNetwork(req.ctx, req.params.id, function(err) { - if (err) { - return res.status(err.code).send(err.msg); - } + TenantNetworksAPI.deleteTenantNetwork(req.ctx, req.params.id, function(err) { + if (err) { + return res.status(err.code).send(err.msg); + } - return res.status(200).end(); - }); + return res.status(200).end(); + }); }); /** @@ -139,14 +138,14 @@ OAE.globalAdminRouter.on('delete', '/api/tenantNetwork/:id', function(req, res) * @HttpResponse 401 Must be a global administrator user to update a tenant network */ OAE.globalAdminRouter.on('post', '/api/tenantNetwork/:id/addTenants', function(req, res) { - var tenantAliases = _.isString(req.body.alias) ? [req.body.alias] : req.body.alias; - TenantNetworksAPI.addTenantAliases(req.ctx, req.params.id, tenantAliases, function(err) { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(200).end(); - }); + const tenantAliases = _.isString(req.body.alias) ? [req.body.alias] : req.body.alias; + TenantNetworksAPI.addTenantAliases(req.ctx, req.params.id, tenantAliases, function(err) { + if (err) { + return res.status(err.code).send(err.msg); + } + + return res.status(200).end(); + }); }); /** @@ -167,20 +166,19 @@ OAE.globalAdminRouter.on('post', '/api/tenantNetwork/:id/addTenants', function(r * @HttpResponse 401 Must be a global administrator user to update a tenant network */ OAE.globalAdminRouter.on('post', '/api/tenantNetwork/:id/removeTenants', function(req, res) { - var tenantAliases = _.isString(req.body.alias) ? [req.body.alias] : req.body.alias; - TenantNetworksAPI.removeTenantAliases(req.ctx, req.params.id, tenantAliases, function(err) { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(200).end(); - }); -}); + const tenantAliases = _.isString(req.body.alias) ? [req.body.alias] : req.body.alias; + TenantNetworksAPI.removeTenantAliases(req.ctx, req.params.id, tenantAliases, function(err) { + if (err) { + return res.status(err.code).send(err.msg); + } + return res.status(200).end(); + }); +}); -///////////// +/// ////////// // TENANTS // -///////////// +/// ////////// /** * @REST postTenantCreate @@ -207,14 +205,15 @@ OAE.globalAdminRouter.on('post', '/api/tenantNetwork/:id/removeTenants', functio * @HttpResponse 400 The tenant alias should not contain a space */ OAE.globalAdminRouter.on('post', '/api/tenant/create', function(req, res) { - var opts = _.oaeExtendDefined({}, _.pick(req.body, 'countryCode')); - opts.emailDomains = OaeUtil.toArray(req.body.emailDomains); - TenantsAPI.createTenant(req.ctx, req.body.alias, req.body.displayName, req.body.host, opts, function(err, tenant) { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(200).send(tenant); - }); + const opts = _.oaeExtendDefined({}, _.pick(req.body, 'countryCode')); + opts.emailDomains = OaeUtil.toArray(req.body.emailDomains); + TenantsAPI.createTenant(req.ctx, req.body.alias, req.body.displayName, req.body.host, opts, function(err, tenant) { + if (err) { + return res.status(err.code).send(err.msg); + } + + res.status(200).send(tenant); + }); }); /** @@ -229,7 +228,7 @@ OAE.globalAdminRouter.on('post', '/api/tenant/create', function(req, res) { * @HttpResponse 200 Tenants available */ OAE.globalAdminRouter.on('get', '/api/tenants', function(req, res) { - res.status(200).send(TenantsAPI.getTenants()); + res.status(200).send(TenantsAPI.getTenants()); }); /** @@ -245,13 +244,13 @@ OAE.globalAdminRouter.on('get', '/api/tenants', function(req, res) { * @HttpResponse 200 The tenants for each email address */ OAE.tenantRouter.on('get', '/api/tenantsByEmail', function(req, res) { - var emails = OaeUtil.toArray(req.query.emails); - if (_.isEmpty(emails)) { - return res.status(400).send('Missing emails parameter'); - } else { - var tenants = TenantsAPI.getTenantsForEmailDomains(emails); - return res.status(200).send(tenants); - } + const emails = OaeUtil.toArray(req.query.emails); + if (_.isEmpty(emails)) { + return res.status(400).send('Missing emails parameter'); + } + + const tenants = TenantsAPI.getTenantsForEmailDomains(emails); + return res.status(200).send(tenants); }); /** @@ -265,8 +264,8 @@ OAE.tenantRouter.on('get', '/api/tenantsByEmail', function(req, res) { * @Return {Tenant} The current tenant * @HttpResponse 200 Current tenant available */ -var _getCurrentTenant = function(req, res) { - res.status(200).send(req.ctx.tenant()); +const _getCurrentTenant = function(req, res) { + res.status(200).send(req.ctx.tenant()); }; OAE.globalAdminRouter.on('get', '/api/tenant', _getCurrentTenant); @@ -286,11 +285,12 @@ OAE.tenantRouter.on('get', '/api/tenant', _getCurrentTenant); * @HttpResponse 404 There is no tenant with alias ... */ OAE.globalAdminRouter.on('get', '/api/tenant/:alias', function(req, res) { - var tenant = TenantsAPI.getTenant(req.params.alias); - if (!tenant) { - return res.status(404).send('There is no tenant with alias ' + req.params.alias); - } - res.status(200).send(tenant); + const tenant = TenantsAPI.getTenant(req.params.alias); + if (!tenant) { + return res.status(404).send('There is no tenant with alias ' + req.params.alias); + } + + res.status(200).send(tenant); }); /** @@ -317,16 +317,18 @@ OAE.globalAdminRouter.on('get', '/api/tenant/:alias', function(req, res) { * @HttpResponse 404 Tenant with alias ... does not exist and cannot be updated */ OAE.tenantRouter.on('post', '/api/tenant', function(req, res) { - var update = _.oaeExtendDefined({}, _.pick(req.body, 'displayName', 'host', 'countryCode')); - if (_.has(req.body, 'emailDomains')) { - update.emailDomains = OaeUtil.toArray(req.body.emailDomains); + const update = _.oaeExtendDefined({}, _.pick(req.body, 'displayName', 'host', 'countryCode')); + if (_.has(req.body, 'emailDomains')) { + update.emailDomains = OaeUtil.toArray(req.body.emailDomains); + } + + TenantsAPI.updateTenant(req.ctx, req.ctx.tenant().alias, update, function(err) { + if (err) { + return res.status(err.code).send(err.msg); } - TenantsAPI.updateTenant(req.ctx, req.ctx.tenant().alias, update, function(err) { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(200).end(); - }); + + res.status(200).end(); + }); }); /** @@ -345,13 +347,14 @@ OAE.tenantRouter.on('post', '/api/tenant', function(req, res) { * @HttpResponse 404 Tenant with alias ... does not exist and cannot be enabled or disabled */ OAE.globalAdminRouter.on('post', '/api/tenant/start', function(req, res) { - // Sets the tenant to be ENABLED by passing in 'false' - TenantsAPI.disableTenants(req.ctx, req.body.aliases, false, function(err) { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(200).end(); - }); + // Sets the tenant to be ENABLED by passing in 'false' + TenantsAPI.disableTenants(req.ctx, req.body.aliases, false, function(err) { + if (err) { + return res.status(err.code).send(err.msg); + } + + res.status(200).end(); + }); }); /** @@ -370,13 +373,14 @@ OAE.globalAdminRouter.on('post', '/api/tenant/start', function(req, res) { * @HttpResponse 404 Tenant with alias ... does not exist and cannot be enabled or disabled */ OAE.globalAdminRouter.on('post', '/api/tenant/stop', function(req, res) { - // Sets the tenant to be disabled by passing in 'true' - TenantsAPI.disableTenants(req.ctx, req.body.aliases, true, function(err) { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(200).end(); - }); + // Sets the tenant to be disabled by passing in 'true' + TenantsAPI.disableTenants(req.ctx, req.body.aliases, true, function(err) { + if (err) { + return res.status(err.code).send(err.msg); + } + + res.status(200).end(); + }); }); /** @@ -404,22 +408,23 @@ OAE.globalAdminRouter.on('post', '/api/tenant/stop', function(req, res) { * @HttpResponse 404 Tenant with alias ... does not exist and cannot be updated */ OAE.globalAdminRouter.on('post', '/api/tenant/:alias', function(req, res) { - var update = _.oaeExtendDefined({}, _.pick(req.body, 'displayName', 'host', 'countryCode')); - if (_.has(req.body, 'emailDomains')) { - update.emailDomains = OaeUtil.toArray(req.body.emailDomains); + const update = _.oaeExtendDefined({}, _.pick(req.body, 'displayName', 'host', 'countryCode')); + if (_.has(req.body, 'emailDomains')) { + update.emailDomains = OaeUtil.toArray(req.body.emailDomains); + } + + TenantsAPI.updateTenant(req.ctx, req.params.alias, update, function(err) { + if (err) { + return res.status(err.code).send(err.msg); } - TenantsAPI.updateTenant(req.ctx, req.params.alias, update, function(err) { - if (err) { - return res.status(err.code).send(err.msg); - } - res.status(200).end(); - }); -}); + res.status(200).end(); + }); +}); -////////////////////////// +/// /////////////////////// // TENANT LANDING PAGES // -////////////////////////// +/// /////////////////////// /** * @REST getTenantLandingPage @@ -433,6 +438,6 @@ OAE.globalAdminRouter.on('post', '/api/tenant/:alias', function(req, res) { * @HttpResponse 200 The landing page information */ OAE.tenantRouter.on('get', '/api/tenant/landingPage', function(req, res) { - var landingPage = TenantsAPI.getLandingPage(req.ctx); - return res.status(200).send(landingPage); + const landingPage = TenantsAPI.getLandingPage(req.ctx); + return res.status(200).send(landingPage); }); diff --git a/packages/oae-tenants/lib/test/util.js b/packages/oae-tenants/lib/test/util.js index 3d4063da41..416faf5c3d 100644 --- a/packages/oae-tenants/lib/test/util.js +++ b/packages/oae-tenants/lib/test/util.js @@ -13,15 +13,15 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const util = require('util'); -const ShortId = require('shortid'); - -const ConfigTestUtil = require('oae-config/lib/test/util'); -const Counter = require('oae-util/lib/counter'); -const RestAPI = require('oae-rest'); - -const TenantsAPI = require('oae-tenants'); +import assert from 'assert'; +import util from 'util'; +import ShortId from 'shortid'; +import Counter from 'oae-util/lib/counter'; +import { generateRandomText } from 'oae-tests'; +import * as TestsUtil from 'oae-tests'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TenantsAPI from 'oae-tenants'; // Keep track of the asynchronous operations that are still pending in the Tenants API const asyncOperationsCounter = new Counter(); @@ -59,7 +59,6 @@ const generateTestTenants = function(globalAdminRestCtx, numToCreate, callback, // Create a tenant with random data const alias = generateTestTenantAlias(); - const TestsUtil = require('oae-tests'); const description = TestsUtil.generateRandomText(); const host = generateTestTenantHost(null, TestsUtil.generateRandomText()); createTenantAndWait(globalAdminRestCtx, alias, description, host, { emailDomains: host }, (err, tenant) => { @@ -189,7 +188,7 @@ const generateTestTenantAlias = function(seed) { const generateTestTenantHost = function(seed, randomText) { seed = seed || 'host'; // This is so wrong - randomText = randomText || require('oae-tests').generateRandomText(); + randomText = randomText || generateRandomText(); return util.format('%s-%s.local', seed, randomText); }; @@ -206,13 +205,14 @@ const clearTenantLandingPage = function(adminRestContext, callback) { const blockName = util.format('block_%d', i); config['oae-tenants/' + blockName + '/type'] = 'empty'; } + ConfigTestUtil.updateConfigAndWait(adminRestContext, null, config, err => { assert.ok(!err); return callback(); }); }; -module.exports = { +export { whenTenantChangePropagated, generateTestTenants, createTenantAndWait, diff --git a/packages/oae-tenants/lib/util.js b/packages/oae-tenants/lib/util.js index c61b932034..6aeb637f83 100644 --- a/packages/oae-tenants/lib/util.js +++ b/packages/oae-tenants/lib/util.js @@ -13,8 +13,10 @@ * permissions and limitations under the License. */ -const Server = require('oae-util/lib/server'); -const TenantsConfig = require('oae-config').config('oae-tenants'); +import * as Server from 'oae-util/lib/server'; +import { setUpConfig } from 'oae-config'; + +const TenantsConfig = setUpConfig('oae-tenants'); /** * Determine whether or not the given context represents a session that is authenticated to the specified tenant. @@ -85,10 +87,4 @@ const getBaseUrl = function(tenant) { return protocol + '://' + tenant.host; }; -module.exports = { - isLoggedIn, - isPrivate, - canInteract, - canInviteGuests, - getBaseUrl -}; +export { isLoggedIn, isPrivate, canInteract, canInviteGuests, getBaseUrl }; diff --git a/packages/oae-tenants/tests/test-landing-pages.js b/packages/oae-tenants/tests/test-landing-pages.js index 14eb895286..e214252be4 100644 --- a/packages/oae-tenants/tests/test-landing-pages.js +++ b/packages/oae-tenants/tests/test-landing-pages.js @@ -13,197 +13,187 @@ * permissions and limitations under the License. */ -const assert = require('assert'); +import assert from 'assert'; -const ConfigTestUtil = require('oae-config/lib/test/util'); -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); - -const TenantsTestUtil = require('oae-tenants/lib/test/util'); +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; describe('Tenant Landing Pages', () => { + // Rest context that can be used every time we need to make a request as an anonymous user + let anonymousCamRestContext = null; + // Rest context that can be used every time we need to use a request as a global admin + let globalAdminRestContext = null; + + /** + * Function that will fill up the anonymous and the tenant admin context + */ + before(callback => { + // Fill up anonymous rest context + anonymousCamRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); + // Fill up the global admin rest context + globalAdminRestContext = TestsUtil.createGlobalAdminRestContext(); + return callback(); + }); + + /** + * Test that verifies that attributes are returned + */ + it('verify the attributes are returned', callback => { + TestsUtil.setupMultiTenantPrivacyEntities(tenant => { + // Clear the default landing page + TenantsTestUtil.clearTenantLandingPage(tenant.adminRestContext, () => { + // Configure all the attributes for the first block + const configUpdate = {}; + configUpdate['oae-tenants/block_1/type'] = 'text'; + configUpdate['oae-tenants/block_1/text/default'] = 'some text'; + configUpdate['oae-tenants/block_1/xs'] = '100%'; + configUpdate['oae-tenants/block_1/sm'] = '100%'; + configUpdate['oae-tenants/block_1/md'] = '100%'; + configUpdate['oae-tenants/block_1/lg'] = '100%'; + configUpdate['oae-tenants/block_1/minHeight'] = '100'; + configUpdate['oae-tenants/block_1/horizontalAlign'] = 'left'; + configUpdate['oae-tenants/block_1/verticalAlign'] = 'top'; + configUpdate['oae-tenants/block_1/bgColor'] = 'red'; + configUpdate['oae-tenants/block_1/titleColor'] = 'green'; + configUpdate['oae-tenants/block_1/textColor'] = 'blue'; + configUpdate['oae-tenants/block_1/icon'] = 'https://foo.com/icon.png'; + configUpdate['oae-tenants/block_1/imgUrl'] = 'https://foo.com/img.png'; + configUpdate['oae-tenants/block_1/videoUrl'] = 'https://foo.com/video.wav'; + configUpdate['oae-tenants/block_1/videoPlaceholder'] = 'https://foo.com/video.placeholder.png'; + ConfigTestUtil.updateConfigAndWait(globalAdminRestContext, tenant.tenant.alias, configUpdate, () => { + // Get the landing page information + const anonymousRestContext = TestsUtil.createTenantRestContext(tenant.tenant.host); + RestAPI.Tenants.getLandingPage(anonymousRestContext, (err, landingPage) => { + assert.ok(!err); - // Rest context that can be used every time we need to make a request as an anonymous user - let anonymousCamRestContext = null; - // Rest context that can be used every time we need to use a request as a global admin - let globalAdminRestContext = null; - - /** - * Function that will fill up the anonymous and the tenant admin context - */ - before((callback) => { - // Fill up anonymous rest context - anonymousCamRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host); - // Fill up the global admin rest context - globalAdminRestContext = TestsUtil.createGlobalAdminRestContext(); - return callback(); - }); + // Only 1 block has been configured + assert.strictEqual(landingPage.length, 1); + + // Verify all attributes are returned + assert.strictEqual(landingPage[0].type, 'text'); + assert.strictEqual(landingPage[0].text, 'some text'); + assert.strictEqual(landingPage[0].xs, '100%'); + assert.strictEqual(landingPage[0].sm, '100%'); + assert.strictEqual(landingPage[0].md, '100%'); + assert.strictEqual(landingPage[0].lg, '100%'); + assert.strictEqual(landingPage[0].minHeight, '100'); + assert.strictEqual(landingPage[0].horizontalAlign, 'left'); + assert.strictEqual(landingPage[0].verticalAlign, 'top'); + assert.strictEqual(landingPage[0].bgColor, 'red'); + assert.strictEqual(landingPage[0].titleColor, 'green'); + assert.strictEqual(landingPage[0].textColor, 'blue'); + assert.strictEqual(landingPage[0].icon, 'https://foo.com/icon.png'); + assert.strictEqual(landingPage[0].imgUrl, 'https://foo.com/img.png'); + assert.strictEqual(landingPage[0].videoUrl, 'https://foo.com/video.wav'); + assert.strictEqual(landingPage[0].videoPlaceholder, 'https://foo.com/video.placeholder.png'); - /** - * Test that verifies that attributes are returned - */ - it('verify the attributes are returned', (callback) => { - TestsUtil.setupMultiTenantPrivacyEntities((tenant) => { - - // Clear the default landing page - TenantsTestUtil.clearTenantLandingPage(tenant.adminRestContext, () => { - - // Configure all the attributes for the first block - const configUpdate = {}; - configUpdate['oae-tenants/block_1/type'] = 'text'; - configUpdate['oae-tenants/block_1/text/default'] = 'some text'; - configUpdate['oae-tenants/block_1/xs'] = '100%'; - configUpdate['oae-tenants/block_1/sm'] = '100%'; - configUpdate['oae-tenants/block_1/md'] = '100%'; - configUpdate['oae-tenants/block_1/lg'] = '100%'; - configUpdate['oae-tenants/block_1/minHeight'] = '100'; - configUpdate['oae-tenants/block_1/horizontalAlign'] = 'left'; - configUpdate['oae-tenants/block_1/verticalAlign'] = 'top'; - configUpdate['oae-tenants/block_1/bgColor'] = 'red'; - configUpdate['oae-tenants/block_1/titleColor'] = 'green'; - configUpdate['oae-tenants/block_1/textColor'] = 'blue'; - configUpdate['oae-tenants/block_1/icon'] = 'https://foo.com/icon.png'; - configUpdate['oae-tenants/block_1/imgUrl'] = 'https://foo.com/img.png'; - configUpdate['oae-tenants/block_1/videoUrl'] = 'https://foo.com/video.wav'; - configUpdate['oae-tenants/block_1/videoPlaceholder'] = 'https://foo.com/video.placeholder.png'; - ConfigTestUtil.updateConfigAndWait(globalAdminRestContext, tenant.tenant.alias, configUpdate, () => { - - // Get the landing page information - const anonymousRestContext = TestsUtil.createTenantRestContext(tenant.tenant.host); - RestAPI.Tenants.getLandingPage(anonymousRestContext, (err, landingPage) => { - assert.ok(!err); - - // Only 1 block has been configured - assert.strictEqual(landingPage.length, 1); - - // Verify all attributes are returned - assert.strictEqual(landingPage[0].type, 'text'); - assert.strictEqual(landingPage[0].text, 'some text'); - assert.strictEqual(landingPage[0].xs, '100%'); - assert.strictEqual(landingPage[0].sm, '100%'); - assert.strictEqual(landingPage[0].md, '100%'); - assert.strictEqual(landingPage[0].lg, '100%'); - assert.strictEqual(landingPage[0].minHeight, '100'); - assert.strictEqual(landingPage[0].horizontalAlign, 'left'); - assert.strictEqual(landingPage[0].verticalAlign, 'top'); - assert.strictEqual(landingPage[0].bgColor, 'red'); - assert.strictEqual(landingPage[0].titleColor, 'green'); - assert.strictEqual(landingPage[0].textColor, 'blue'); - assert.strictEqual(landingPage[0].icon, 'https://foo.com/icon.png'); - assert.strictEqual(landingPage[0].imgUrl, 'https://foo.com/img.png'); - assert.strictEqual(landingPage[0].videoUrl, 'https://foo.com/video.wav'); - assert.strictEqual(landingPage[0].videoPlaceholder, 'https://foo.com/video.placeholder.png'); - - return callback(); - }); - }); - }); + return callback(); + }); }); + }); }); + }); + + /** + * Test that verifies that only non-empty blocks are returned + */ + it('verify empty blocks are not returned', callback => { + TestsUtil.setupMultiTenantPrivacyEntities(tenant => { + // Clear the default landing page + TenantsTestUtil.clearTenantLandingPage(tenant.adminRestContext, () => { + // Configure 1 block on the the tenant's landing page + const configUpdate = {}; + configUpdate['oae-tenants/block_1/type'] = 'text'; + configUpdate['oae-tenants/block_1/text/default'] = 'some text'; + ConfigTestUtil.updateConfigAndWait(globalAdminRestContext, tenant.tenant.alias, configUpdate, () => { + // Get the landing page information + const anonymousRestContext = TestsUtil.createTenantRestContext(tenant.tenant.host); + RestAPI.Tenants.getLandingPage(anonymousRestContext, (err, landingPage) => { + assert.ok(!err); - /** - * Test that verifies that only non-empty blocks are returned - */ - it('verify empty blocks are not returned', (callback) => { - TestsUtil.setupMultiTenantPrivacyEntities((tenant) => { - - // Clear the default landing page - TenantsTestUtil.clearTenantLandingPage(tenant.adminRestContext, () => { - - // Configure 1 block on the the tenant's landing page - const configUpdate = {}; - configUpdate['oae-tenants/block_1/type'] = 'text'; - configUpdate['oae-tenants/block_1/text/default'] = 'some text'; - ConfigTestUtil.updateConfigAndWait(globalAdminRestContext, tenant.tenant.alias, configUpdate, () => { - - // Get the landing page information - const anonymousRestContext = TestsUtil.createTenantRestContext(tenant.tenant.host); - RestAPI.Tenants.getLandingPage(anonymousRestContext, (err, landingPage) => { - assert.ok(!err); - - // Only 1 block has been configured - assert.strictEqual(landingPage.length, 1); + // Only 1 block has been configured + assert.strictEqual(landingPage.length, 1); - return callback(); - }); - }); - }); + return callback(); + }); }); + }); }); - - /** - * Test that verifies that text attributes are internationalizable - */ - it('verify that text attributes are internationalizable', (callback) => { - TestsUtil.setupMultiTenantPrivacyEntities((tenant) => { - - // Configure the tenant's landing page - const configUpdate = {}; - configUpdate['oae-tenants/block_1/type'] = 'text'; - configUpdate['oae-tenants/block_1/text/default'] = 'default text'; - configUpdate['oae-tenants/block_1/text/fr_FR'] = 'French text'; - ConfigTestUtil.updateConfigAndWait(globalAdminRestContext, tenant.tenant.alias, configUpdate, () => { - - const anonymousRestContext = TestsUtil.createTenantRestContext(tenant.tenant.host); - RestAPI.Tenants.getLandingPage(anonymousRestContext, (err, landingPage) => { + }); + + /** + * Test that verifies that text attributes are internationalizable + */ + it('verify that text attributes are internationalizable', callback => { + TestsUtil.setupMultiTenantPrivacyEntities(tenant => { + // Configure the tenant's landing page + const configUpdate = {}; + configUpdate['oae-tenants/block_1/type'] = 'text'; + configUpdate['oae-tenants/block_1/text/default'] = 'default text'; + configUpdate['oae-tenants/block_1/text/fr_FR'] = 'French text'; + ConfigTestUtil.updateConfigAndWait(globalAdminRestContext, tenant.tenant.alias, configUpdate, () => { + const anonymousRestContext = TestsUtil.createTenantRestContext(tenant.tenant.host); + RestAPI.Tenants.getLandingPage(anonymousRestContext, (err, landingPage) => { + assert.ok(!err); + + // Verify the default text was returned + assert.strictEqual(landingPage[0].text, 'default text'); + + // Generate some test users + TestsUtil.generateTestUsers(tenant.adminRestContext, 3, (err, users, frenchUser, defaultUser, hindiUser) => { + // Set a user's locale to French + RestAPI.User.updateUser(frenchUser.restContext, frenchUser.user.id, { locale: 'fr_FR' }, err => { + assert.ok(!err); + + // Get the landing page information with the French user + RestAPI.Tenants.getLandingPage(frenchUser.restContext, (err, landingPage) => { + assert.ok(!err); + + // Verify the French text was returned + assert.strictEqual(landingPage[0].text, 'French text'); + + // Get the landing page information with a user who has no configured locale + RestAPI.Tenants.getLandingPage(defaultUser.restContext, (err, landingPage) => { + assert.ok(!err); + + // Verify the default text was returned + assert.strictEqual(landingPage[0].text, 'default text'); + + // Set a user's locale to Hindi + RestAPI.User.updateUser(hindiUser.restContext, hindiUser.user.id, { locale: 'hi_IN' }, err => { assert.ok(!err); - // Verify the default text was returned - assert.strictEqual(landingPage[0].text, 'default text'); - - // Generate some test users - TestsUtil.generateTestUsers(tenant.adminRestContext, 3, (err, users, frenchUser, defaultUser, hindiUser) => { - - // Set a user's locale to French - RestAPI.User.updateUser(frenchUser.restContext, frenchUser.user.id, {'locale': 'fr_FR'}, (err) => { - assert.ok(!err); - - // Get the landing page information with the French user - RestAPI.Tenants.getLandingPage(frenchUser.restContext, (err, landingPage) => { - assert.ok(!err); - - // Verify the French text was returned - assert.strictEqual(landingPage[0].text, 'French text'); - - // Get the landing page information with a user who has no configured locale - RestAPI.Tenants.getLandingPage(defaultUser.restContext, (err, landingPage) => { - assert.ok(!err); + // Get the landing page information with the Hindi user + RestAPI.Tenants.getLandingPage(hindiUser.restContext, function(err, landingPage) { + assert.ok(!err); - // Verify the default text was returned - assert.strictEqual(landingPage[0].text, 'default text'); - - // Set a user's locale to Hindi - RestAPI.User.updateUser(hindiUser.restContext, hindiUser.user.id, {'locale': 'hi_IN'}, (err) => { - assert.ok(!err); - - // Get the landing page information with the Hindi user - RestAPI.Tenants.getLandingPage(hindiUser.restContext, function(err, landingPage) { - assert.ok(!err); - - // Verify the default text was returned - assert.strictEqual(landingPage[0].text, 'default text'); - return callback(); - }); - }); - }); - }); - }); + // Verify the default text was returned + assert.strictEqual(landingPage[0].text, 'default text'); + return callback(); }); + }); }); + }); }); + }); }); + }); }); - - /** - * Test that verifies that blocks are not returned in the config - */ - it('verify that blocks are not returned in the config', (callback) => { - RestAPI.Config.getTenantConfig(anonymousCamRestContext, null, (err, config) => { - assert.ok(!err); - for (let i = 1; i <= 12; i++) { - assert.ok(!config['oae-tenants']['block_' + i]); - } - return callback(); - }); + }); + + /** + * Test that verifies that blocks are not returned in the config + */ + it('verify that blocks are not returned in the config', callback => { + RestAPI.Config.getTenantConfig(anonymousCamRestContext, null, (err, config) => { + assert.ok(!err); + for (let i = 1; i <= 12; i++) { + assert.ok(!config['oae-tenants']['block_' + i]); + } + + return callback(); }); + }); }); diff --git a/packages/oae-tenants/tests/test-tenant-networks.js b/packages/oae-tenants/tests/test-tenant-networks.js index 863d49559d..32dfd51f66 100644 --- a/packages/oae-tenants/tests/test-tenant-networks.js +++ b/packages/oae-tenants/tests/test-tenant-networks.js @@ -12,18 +12,16 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const Cassandra = require('oae-util/lib/cassandra'); -const Pubsub = require('oae-util/lib/pubsub'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const TestsUtil = require('oae-tests'); - -const TenantNetworksAPI = require('oae-tenants/lib/api.networks'); -const TenantNetworksDAO = require('oae-tenants/lib/internal/dao.networks'); -const TenantsTestUtil = require('oae-tenants/lib/test/util'); +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as Pubsub from 'oae-util/lib/pubsub'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as TenantNetworksAPI from 'oae-tenants/lib/api.networks'; +import * as TenantNetworksDAO from 'oae-tenants/lib/internal/dao.networks'; +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; describe('Tenant Networks', () => { // Standard REST contexts to use to execute requests as different types of users @@ -165,10 +163,7 @@ describe('Tenant Networks', () => { 'verifies create tenant network validation', (err, tenantNetwork) => { assert.ok(!err); - assert.strictEqual( - tenantNetwork.displayName, - 'verifies create tenant network validation' - ); + assert.strictEqual(tenantNetwork.displayName, 'verifies create tenant network validation'); return callback(); } ); @@ -226,10 +221,7 @@ describe('Tenant Networks', () => { (err, tenantNetwork) => { assert.ok(!err); assert.ok(_.isObject(tenantNetwork)); - assert.strictEqual( - tenantNetwork.displayName, - 'verifies create tenant network authorization' - ); + assert.strictEqual(tenantNetwork.displayName, 'verifies create tenant network authorization'); return callback(); } ); @@ -300,48 +292,42 @@ describe('Tenant Networks', () => { assert.ok(!tenantNetwork); // Ensure the tenant network displayName hasn't been updated somehow - RestAPI.Tenants.getTenantNetworks( - globalAdminRestContext, - (err, tenantNetworks) => { - assert.ok(!err); - assert.ok(tenantNetworks[originalTenantNetwork.id]); - assert.strictEqual( - tenantNetworks[originalTenantNetwork.id].displayName, - originalTenantNetwork.displayName - ); + RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { + assert.ok(!err); + assert.ok(tenantNetworks[originalTenantNetwork.id]); + assert.strictEqual( + tenantNetworks[originalTenantNetwork.id].displayName, + originalTenantNetwork.displayName + ); - // Sanity check updating the tenant network's display name - RestAPI.Tenants.updateTenantNetwork( - globalAdminRestContext, - originalTenantNetwork.id, - 'verifies update tenant network validation', - (err, tenantNetwork) => { + // Sanity check updating the tenant network's display name + RestAPI.Tenants.updateTenantNetwork( + globalAdminRestContext, + originalTenantNetwork.id, + 'verifies update tenant network validation', + (err, tenantNetwork) => { + assert.ok(!err); + assert.ok(tenantNetwork); + assert.strictEqual(tenantNetwork.id, originalTenantNetwork.id); + assert.strictEqual( + tenantNetwork.displayName, + 'verifies update tenant network validation' + ); + + // Ensure the displayName has changed when fetching + RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { assert.ok(!err); - assert.ok(tenantNetwork); - assert.strictEqual(tenantNetwork.id, originalTenantNetwork.id); + assert.ok(tenantNetworks[tenantNetwork.id]); assert.strictEqual( - tenantNetwork.displayName, + tenantNetworks[tenantNetwork.id].displayName, 'verifies update tenant network validation' ); - // Ensure the displayName has changed when fetching - RestAPI.Tenants.getTenantNetworks( - globalAdminRestContext, - (err, tenantNetworks) => { - assert.ok(!err); - assert.ok(tenantNetworks[tenantNetwork.id]); - assert.strictEqual( - tenantNetworks[tenantNetwork.id].displayName, - 'verifies update tenant network validation' - ); - - return callback(); - } - ); - } - ); - } - ); + return callback(); + }); + } + ); + }); } ); } @@ -404,34 +390,31 @@ describe('Tenant Networks', () => { assert.ok(!tenantNetwork); // Ensure the tenant network displayName has not changed - RestAPI.Tenants.getTenantNetworks( - globalAdminRestContext, - (err, tenantNetworks) => { - assert.ok(!err); - assert.ok(tenantNetworks); - assert.ok(tenantNetworks[originalTenantNetwork.id]); - assert.strictEqual( - tenantNetworks[originalTenantNetwork.id].displayName, - originalTenantNetwork.displayName - ); + RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { + assert.ok(!err); + assert.ok(tenantNetworks); + assert.ok(tenantNetworks[originalTenantNetwork.id]); + assert.strictEqual( + tenantNetworks[originalTenantNetwork.id].displayName, + originalTenantNetwork.displayName + ); - // Sanity check that updating as global admin user succeeds - RestAPI.Tenants.updateTenantNetwork( - globalAdminRestContext, - originalTenantNetwork.id, - 'verifies update tenant network authorization', - (err, tenantNetwork) => { - assert.ok(!err); - assert.ok(_.isObject(tenantNetwork)); - assert.strictEqual( - tenantNetwork.displayName, - 'verifies update tenant network authorization' - ); - return callback(); - } - ); - } - ); + // Sanity check that updating as global admin user succeeds + RestAPI.Tenants.updateTenantNetwork( + globalAdminRestContext, + originalTenantNetwork.id, + 'verifies update tenant network authorization', + (err, tenantNetwork) => { + assert.ok(!err); + assert.ok(_.isObject(tenantNetwork)); + assert.strictEqual( + tenantNetwork.displayName, + 'verifies update tenant network authorization' + ); + return callback(); + } + ); + }); } ); } @@ -465,45 +448,34 @@ describe('Tenant Networks', () => { assert.strictEqual(err.code, 400); // Ensure deleting a non-existing tenant network results in a 404 - RestAPI.Tenants.deleteTenantNetwork( - globalAdminRestContext, - 'non-existing-tenant-network-id', - err => { - assert.ok(err); - assert.ok(err.code, 404); + RestAPI.Tenants.deleteTenantNetwork(globalAdminRestContext, 'non-existing-tenant-network-id', err => { + assert.ok(err); + assert.ok(err.code, 404); - // Ensure the tenant network still exists - RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { - assert.ok(!err); - assert.ok(tenantNetworks); - assert.ok(tenantNetworks[originalTenantNetwork.id]); - assert.strictEqual( - tenantNetworks[originalTenantNetwork.id].displayName, - originalTenantNetwork.displayName - ); + // Ensure the tenant network still exists + RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { + assert.ok(!err); + assert.ok(tenantNetworks); + assert.ok(tenantNetworks[originalTenantNetwork.id]); + assert.strictEqual( + tenantNetworks[originalTenantNetwork.id].displayName, + originalTenantNetwork.displayName + ); - // Sanity check a true tenant network delete - RestAPI.Tenants.deleteTenantNetwork( - globalAdminRestContext, - originalTenantNetwork.id, - err => { - assert.ok(!err); + // Sanity check a true tenant network delete + RestAPI.Tenants.deleteTenantNetwork(globalAdminRestContext, originalTenantNetwork.id, err => { + assert.ok(!err); - // Ensure the tenant network no longer exists - RestAPI.Tenants.getTenantNetworks( - globalAdminRestContext, - (err, tenantNetworks) => { - assert.ok(!err); - assert.ok(tenantNetworks); - assert.ok(!tenantNetworks[originalTenantNetwork.id]); - return callback(); - } - ); - } - ); + // Ensure the tenant network no longer exists + RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { + assert.ok(!err); + assert.ok(tenantNetworks); + assert.ok(!tenantNetworks[originalTenantNetwork.id]); + return callback(); + }); }); - } - ); + }); + }); }); }); }); @@ -519,78 +491,52 @@ describe('Tenant Networks', () => { assert.ok(!err); // Ensure deleting as anonymous user-tenant user results in a 404 (because the endpoint is not bound to the user tenant server) - RestAPI.Tenants.deleteTenantNetwork( - anonymousCamRestContext, - originalTenantNetwork.id, - err => { + RestAPI.Tenants.deleteTenantNetwork(anonymousCamRestContext, originalTenantNetwork.id, err => { + assert.ok(err); + assert.strictEqual(err.code, 404); + + // Ensure deleting as loggedin user-tenant user results in a 404 (because the endpoint is not bound to the user tenant server) + RestAPI.Tenants.deleteTenantNetwork(mrvisser.restContext, originalTenantNetwork.id, err => { assert.ok(err); assert.strictEqual(err.code, 404); - // Ensure deleting as loggedin user-tenant user results in a 404 (because the endpoint is not bound to the user tenant server) - RestAPI.Tenants.deleteTenantNetwork( - mrvisser.restContext, - originalTenantNetwork.id, - err => { - assert.ok(err); - assert.strictEqual(err.code, 404); - - // Ensure deleting as tenant administrator user results in a 404 (because the endpoint is not bound to the user tenant server) - RestAPI.Tenants.deleteTenantNetwork( - camAdminRestContext, - originalTenantNetwork.id, - err => { - assert.ok(err); - assert.strictEqual(err.code, 404); + // Ensure deleting as tenant administrator user results in a 404 (because the endpoint is not bound to the user tenant server) + RestAPI.Tenants.deleteTenantNetwork(camAdminRestContext, originalTenantNetwork.id, err => { + assert.ok(err); + assert.strictEqual(err.code, 404); - // Ensure deleting as anonymous global-admin user results in a 401 - RestAPI.Tenants.deleteTenantNetwork( - anonymousGlobalRestContext, - originalTenantNetwork.id, - err => { - assert.ok(err); - assert.strictEqual(err.code, 401); + // Ensure deleting as anonymous global-admin user results in a 401 + RestAPI.Tenants.deleteTenantNetwork(anonymousGlobalRestContext, originalTenantNetwork.id, err => { + assert.ok(err); + assert.strictEqual(err.code, 401); - // Ensure the tenant network is still there - RestAPI.Tenants.getTenantNetworks( - globalAdminRestContext, - (err, tenantNetworks) => { - assert.ok(!err); - assert.ok(tenantNetworks); - assert.ok(tenantNetworks[originalTenantNetwork.id]); - assert.strictEqual( - tenantNetworks[originalTenantNetwork.id].displayName, - originalTenantNetwork.displayName - ); + // Ensure the tenant network is still there + RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { + assert.ok(!err); + assert.ok(tenantNetworks); + assert.ok(tenantNetworks[originalTenantNetwork.id]); + assert.strictEqual( + tenantNetworks[originalTenantNetwork.id].displayName, + originalTenantNetwork.displayName + ); - // Sanity check that deleting as global admin user succeeds - RestAPI.Tenants.deleteTenantNetwork( - globalAdminRestContext, - originalTenantNetwork.id, - err => { - assert.ok(!err); + // Sanity check that deleting as global admin user succeeds + RestAPI.Tenants.deleteTenantNetwork(globalAdminRestContext, originalTenantNetwork.id, err => { + assert.ok(!err); - // Ensure the tenant network is gone - RestAPI.Tenants.getTenantNetworks( - globalAdminRestContext, - (err, tenantNetworks) => { - assert.ok(!err); - assert.ok(tenantNetworks); - assert.ok(!tenantNetworks[originalTenantNetwork.id]); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + // Ensure the tenant network is gone + RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { + assert.ok(!err); + assert.ok(tenantNetworks); + assert.ok(!tenantNetworks[originalTenantNetwork.id]); + return callback(); + }); + }); + }); + }); + }); + }); + }); }); }); }); @@ -644,126 +590,96 @@ describe('Tenant Networks', () => { it('verify add tenant alias validation', callback => { TenantsTestUtil.generateTestTenantNetworks(globalAdminRestContext, 1, tenantNetwork => { // Ensure a tenant network id is required when adding a tenant to a tenant network (we test a 404 because the id is part of the resource path) - RestAPI.Tenants.addTenantAliases( - globalAdminRestContext, - null, - [global.oaeTests.tenants.cam.alias], - err => { - assert.ok(err); - assert.ok(err.code, 404); + RestAPI.Tenants.addTenantAliases(globalAdminRestContext, null, [global.oaeTests.tenants.cam.alias], err => { + assert.ok(err); + assert.ok(err.code, 404); - // Ensure a tenant network id is required when adding a tenant to a tenant network directly against the API - TenantNetworksAPI.addTenantAliases( - TestsUtil.createGlobalAdminContext(), - null, - [global.oaeTests.tenants.cam.alias], - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); + // Ensure a tenant network id is required when adding a tenant to a tenant network directly against the API + TenantNetworksAPI.addTenantAliases( + TestsUtil.createGlobalAdminContext(), + null, + [global.oaeTests.tenants.cam.alias], + err => { + assert.ok(err); + assert.strictEqual(err.code, 400); - // Ensure a tenant network id cannot be all whitespace when adding a tenant to a tenant network - RestAPI.Tenants.addTenantAliases( - globalAdminRestContext, - ' ', - [global.oaeTests.tenants.cam.alias], - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); + // Ensure a tenant network id cannot be all whitespace when adding a tenant to a tenant network + RestAPI.Tenants.addTenantAliases( + globalAdminRestContext, + ' ', + [global.oaeTests.tenants.cam.alias], + err => { + assert.ok(err); + assert.strictEqual(err.code, 400); - // Ensure adding tenants to a non-existing tenant network results in a 404 - RestAPI.Tenants.addTenantAliases( - globalAdminRestContext, - 'non-existing-tenant-network-id', - [global.oaeTests.tenants.cam.alias], - err => { + // Ensure adding tenants to a non-existing tenant network results in a 404 + RestAPI.Tenants.addTenantAliases( + globalAdminRestContext, + 'non-existing-tenant-network-id', + [global.oaeTests.tenants.cam.alias], + err => { + assert.ok(err); + assert.ok(err.code, 404); + + // Ensure a list of tenant aliases is required when adding tenants to a tenant network + RestAPI.Tenants.addTenantAliases(globalAdminRestContext, tenantNetwork.id, null, err => { assert.ok(err); - assert.ok(err.code, 404); + assert.ok(err.code, 400); - // Ensure a list of tenant aliases is required when adding tenants to a tenant network - RestAPI.Tenants.addTenantAliases( - globalAdminRestContext, - tenantNetwork.id, - null, - err => { - assert.ok(err); - assert.ok(err.code, 400); + // Ensure at least one tenant alias must be specified when adding tenants to a tenant network + RestAPI.Tenants.addTenantAliases(globalAdminRestContext, tenantNetwork.id, [], err => { + assert.ok(err); + assert.ok(err.code, 400); - // Ensure at least one tenant alias must be specified when adding tenants to a tenant network - RestAPI.Tenants.addTenantAliases( - globalAdminRestContext, - tenantNetwork.id, - [], - err => { - assert.ok(err); - assert.ok(err.code, 400); + // Ensure all tenants must exist when adding tenants to a tenant network + RestAPI.Tenants.addTenantAliases( + globalAdminRestContext, + tenantNetwork.id, + ['non-existing-tenant-alias', global.oaeTests.tenants.cam.alias], + err => { + assert.ok(err); + assert.ok(err.code, 400); - // Ensure all tenants must exist when adding tenants to a tenant network + // Ensure no tenants have been added to the tenant network + RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { + assert.ok(!err); + assert.ok(tenantNetworks); + assert.ok(tenantNetworks[tenantNetwork.id]); + assert.ok(_.isArray(tenantNetworks[tenantNetwork.id].tenants)); + assert.ok(_.isEmpty(tenantNetworks[tenantNetwork.id].tenants)); + + // Sanity check adding a valid tenant alias to the tenant network RestAPI.Tenants.addTenantAliases( globalAdminRestContext, tenantNetwork.id, - ['non-existing-tenant-alias', global.oaeTests.tenants.cam.alias], + [global.oaeTests.tenants.cam.alias], err => { - assert.ok(err); - assert.ok(err.code, 400); + assert.ok(!err); - // Ensure no tenants have been added to the tenant network - RestAPI.Tenants.getTenantNetworks( - globalAdminRestContext, - (err, tenantNetworks) => { - assert.ok(!err); - assert.ok(tenantNetworks); - assert.ok(tenantNetworks[tenantNetwork.id]); - assert.ok( - _.isArray(tenantNetworks[tenantNetwork.id].tenants) - ); - assert.ok( - _.isEmpty(tenantNetworks[tenantNetwork.id].tenants) - ); - - // Sanity check adding a valid tenant alias to the tenant network - RestAPI.Tenants.addTenantAliases( - globalAdminRestContext, - tenantNetwork.id, - [global.oaeTests.tenants.cam.alias], - err => { - assert.ok(!err); - - // Ensure the tenant is now found in the tenant network response - RestAPI.Tenants.getTenantNetworks( - globalAdminRestContext, - (err, tenantNetworks) => { - assert.ok(!err); - assert.ok(tenantNetworks); - assert.ok(tenantNetworks[tenantNetwork.id]); - assert.ok( - _.isArray(tenantNetworks[tenantNetwork.id].tenants) - ); - assert.strictEqual( - tenantNetworks[tenantNetwork.id].tenants.length, - 1 - ); - - return callback(); - } - ); - } - ); - } - ); + // Ensure the tenant is now found in the tenant network response + RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { + assert.ok(!err); + assert.ok(tenantNetworks); + assert.ok(tenantNetworks[tenantNetwork.id]); + assert.ok(_.isArray(tenantNetworks[tenantNetwork.id].tenants)); + assert.strictEqual(tenantNetworks[tenantNetwork.id].tenants.length, 1); + + return callback(); + }); } ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + }); + } + ); + }); + }); + } + ); + } + ); + } + ); + }); }); }); @@ -812,44 +728,33 @@ describe('Tenant Networks', () => { assert.strictEqual(err.code, 401); // Ensure the tenant network still has no tenants associated to it - RestAPI.Tenants.getTenantNetworks( - globalAdminRestContext, - (err, tenantNetworks) => { - assert.ok(!err); - assert.ok(tenantNetworks); - assert.ok(tenantNetworks[originalTenantNetwork.id]); - assert.ok(_.isArray(tenantNetworks[originalTenantNetwork.id].tenants)); - assert.ok(_.isEmpty(tenantNetworks[originalTenantNetwork.id].tenants)); + RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { + assert.ok(!err); + assert.ok(tenantNetworks); + assert.ok(tenantNetworks[originalTenantNetwork.id]); + assert.ok(_.isArray(tenantNetworks[originalTenantNetwork.id].tenants)); + assert.ok(_.isEmpty(tenantNetworks[originalTenantNetwork.id].tenants)); - // Sanity check that adding a tenant as global admin user succeeds - RestAPI.Tenants.addTenantAliases( - globalAdminRestContext, - originalTenantNetwork.id, - [global.oaeTests.tenants.cam.alias], - err => { - assert.ok(!err); + // Sanity check that adding a tenant as global admin user succeeds + RestAPI.Tenants.addTenantAliases( + globalAdminRestContext, + originalTenantNetwork.id, + [global.oaeTests.tenants.cam.alias], + err => { + assert.ok(!err); - // Ensure the tenant network is gone - RestAPI.Tenants.getTenantNetworks( - globalAdminRestContext, - (err, tenantNetworks) => { - assert.ok(!err); - assert.ok(tenantNetworks); - assert.ok(tenantNetworks[originalTenantNetwork.id]); - assert.ok( - _.isArray(tenantNetworks[originalTenantNetwork.id].tenants) - ); - assert.strictEqual( - tenantNetworks[originalTenantNetwork.id].tenants.length, - 1 - ); - return callback(); - } - ); - } - ); - } - ); + // Ensure the tenant network is gone + RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { + assert.ok(!err); + assert.ok(tenantNetworks); + assert.ok(tenantNetworks[originalTenantNetwork.id]); + assert.ok(_.isArray(tenantNetworks[originalTenantNetwork.id].tenants)); + assert.strictEqual(tenantNetworks[originalTenantNetwork.id].tenants.length, 1); + return callback(); + }); + } + ); + }); } ); } @@ -911,74 +816,46 @@ describe('Tenant Networks', () => { assert.ok(err.code, 404); // Ensure a list of tenant aliases is required when removing tenants from a tenant network - RestAPI.Tenants.removeTenantAliases( - globalAdminRestContext, - tenantNetwork.id, - null, - err => { + RestAPI.Tenants.removeTenantAliases(globalAdminRestContext, tenantNetwork.id, null, err => { + assert.ok(err); + assert.ok(err.code, 400); + + // Ensure at least one tenant alias must be specified when removing tenants from a tenant network + RestAPI.Tenants.removeTenantAliases(globalAdminRestContext, tenantNetwork.id, [], err => { assert.ok(err); assert.ok(err.code, 400); - // Ensure at least one tenant alias must be specified when removing tenants from a tenant network - RestAPI.Tenants.removeTenantAliases( - globalAdminRestContext, - tenantNetwork.id, - [], - err => { - assert.ok(err); - assert.ok(err.code, 400); + // Ensure no tenants have been removed from the tenant network + RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { + assert.ok(!err); + assert.ok(tenantNetworks); + assert.ok(tenantNetworks[tenantNetwork.id]); + assert.ok(_.isArray(tenantNetworks[tenantNetwork.id].tenants)); + assert.strictEqual(tenantNetworks[tenantNetwork.id].tenants.length, 1); - // Ensure no tenants have been removed from the tenant network - RestAPI.Tenants.getTenantNetworks( - globalAdminRestContext, - (err, tenantNetworks) => { + // Sanity check removing a valid tenant alias from the tenant network, also non-existing tenants in the array do not result in a validation error + RestAPI.Tenants.removeTenantAliases( + globalAdminRestContext, + tenantNetwork.id, + ['non-existing-tenant-alias', global.oaeTests.tenants.cam.alias], + err => { + assert.ok(!err); + + // Ensure the tenant is no longer found in the tenant network response + RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { assert.ok(!err); assert.ok(tenantNetworks); assert.ok(tenantNetworks[tenantNetwork.id]); - assert.ok( - _.isArray(tenantNetworks[tenantNetwork.id].tenants) - ); - assert.strictEqual( - tenantNetworks[tenantNetwork.id].tenants.length, - 1 - ); - - // Sanity check removing a valid tenant alias from the tenant network, also non-existing tenants in the array do not result in a validation error - RestAPI.Tenants.removeTenantAliases( - globalAdminRestContext, - tenantNetwork.id, - [ - 'non-existing-tenant-alias', - global.oaeTests.tenants.cam.alias - ], - err => { - assert.ok(!err); - - // Ensure the tenant is no longer found in the tenant network response - RestAPI.Tenants.getTenantNetworks( - globalAdminRestContext, - (err, tenantNetworks) => { - assert.ok(!err); - assert.ok(tenantNetworks); - assert.ok(tenantNetworks[tenantNetwork.id]); - assert.ok( - _.isArray(tenantNetworks[tenantNetwork.id].tenants) - ); - assert.ok( - _.isEmpty(tenantNetworks[tenantNetwork.id].tenants) - ); - - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + assert.ok(_.isArray(tenantNetworks[tenantNetwork.id].tenants)); + assert.ok(_.isEmpty(tenantNetworks[tenantNetwork.id].tenants)); + + return callback(); + }); + } + ); + }); + }); + }); } ); } @@ -1045,47 +922,34 @@ describe('Tenant Networks', () => { assert.strictEqual(err.code, 401); // Ensure the tenant network still has the tenant associated to it - RestAPI.Tenants.getTenantNetworks( - globalAdminRestContext, - (err, tenantNetworks) => { - assert.ok(!err); - assert.ok(tenantNetworks); - assert.ok(tenantNetworks[tenantNetwork.id]); - assert.ok(_.isArray(tenantNetworks[tenantNetwork.id].tenants)); - assert.strictEqual( - tenantNetworks[tenantNetwork.id].tenants.length, - 1 - ); + RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { + assert.ok(!err); + assert.ok(tenantNetworks); + assert.ok(tenantNetworks[tenantNetwork.id]); + assert.ok(_.isArray(tenantNetworks[tenantNetwork.id].tenants)); + assert.strictEqual(tenantNetworks[tenantNetwork.id].tenants.length, 1); - // Sanity check that removing a tenant as global admin user succeeds - RestAPI.Tenants.removeTenantAliases( - globalAdminRestContext, - tenantNetwork.id, - [global.oaeTests.tenants.cam.alias], - err => { + // Sanity check that removing a tenant as global admin user succeeds + RestAPI.Tenants.removeTenantAliases( + globalAdminRestContext, + tenantNetwork.id, + [global.oaeTests.tenants.cam.alias], + err => { + assert.ok(!err); + + // Ensure the tenant network is gone + RestAPI.Tenants.getTenantNetworks(globalAdminRestContext, (err, tenantNetworks) => { assert.ok(!err); + assert.ok(tenantNetworks); + assert.ok(tenantNetworks[tenantNetwork.id]); + assert.ok(_.isArray(tenantNetworks[tenantNetwork.id].tenants)); + assert.ok(_.isEmpty(tenantNetworks[tenantNetwork.id].tenants)); - // Ensure the tenant network is gone - RestAPI.Tenants.getTenantNetworks( - globalAdminRestContext, - (err, tenantNetworks) => { - assert.ok(!err); - assert.ok(tenantNetworks); - assert.ok(tenantNetworks[tenantNetwork.id]); - assert.ok( - _.isArray(tenantNetworks[tenantNetwork.id].tenants) - ); - assert.ok( - _.isEmpty(tenantNetworks[tenantNetwork.id].tenants) - ); - - return callback(); - } - ); - } - ); - } - ); + return callback(); + }); + } + ); + }); } ); } @@ -1105,18 +969,18 @@ describe('Tenant Networks', () => { */ it('verify tenant network mutation operations all result in cluster cache invalidation event', callback => { /*! - * Convenience method that invokes a method and then waits for a pubsub oae-tenant-networks invalidation event. The - * callback is only invoked once both the method request and the invalidation event has been fired. This method will - * hang if one of the cases never happen. - * - * When complete, the callback at the end of the arguments list will be invoked with the callback parameters of the provided - * method that was completed. - * - * @param {Function} method The method to invoke - * @param {Arguments...} arguments The arguments with which to invoke the method. The last argument should be a callback - */ + * Convenience method that invokes a method and then waits for a pubsub oae-tenant-networks invalidation event. The + * callback is only invoked once both the method request and the invalidation event has been fired. This method will + * hang if one of the cases never happen. + * + * When complete, the callback at the end of the arguments list will be invoked with the callback parameters of the provided + * method that was completed. + * + * @param {Function} method The method to invoke + * @param {Arguments...} arguments The arguments with which to invoke the method. The last argument should be a callback + */ const _invokeAndWaitForInvalidate = function(...args) { - let method = _.first(args); + const method = _.first(args); let callbackReturned = false; let invalidateOccurred = false; let callbackArguments = null; diff --git a/packages/oae-tenants/tests/test-tenants.js b/packages/oae-tenants/tests/test-tenants.js index e09ef917c1..990c4d2cb2 100644 --- a/packages/oae-tenants/tests/test-tenants.js +++ b/packages/oae-tenants/tests/test-tenants.js @@ -13,23 +13,20 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const util = require('util'); -const _ = require('underscore'); - -const ConfigAPI = require('oae-config'); -const ConfigTestUtil = require('oae-config/lib/test/util'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const ShibbolethAPI = require('oae-authentication/lib/strategies/shibboleth/api'); -const TestsUtil = require('oae-tests'); - -const TenantsAPI = require('oae-tenants'); -const TenantsEmailDomainIndex = require('oae-tenants/lib/internal/emailDomainIndex'); -const TenantsUtil = require('oae-tenants/lib/util'); -const TenantsTestUtil = require('oae-tenants/lib/test/util'); +import assert from 'assert'; +import util from 'util'; +import _ from 'underscore'; + +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as ShibbolethAPI from 'oae-authentication/lib/strategies/shibboleth/api'; +import * as TestsUtil from 'oae-tests'; +import * as TenantsAPI from 'oae-tenants'; +import TenantsEmailDomainIndex from 'oae-tenants/lib/internal/emailDomainIndex'; +import * as TenantsUtil from 'oae-tenants/lib/util'; +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; describe('Tenants', () => { // Rest context that can be used every time we need to make a request as an anonymous user @@ -103,12 +100,12 @@ describe('Tenants', () => { ]; /*! - * Ensure that the `index.match` function works as expected assuming all the standard - * entries that are inserted using `_createIndex()` - * - * @param {TenantEmailDomainIndex} index The index to test - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure that the `index.match` function works as expected assuming all the standard + * entries that are inserted using `_createIndex()` + * + * @param {TenantEmailDomainIndex} index The index to test + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _assertAllStandardMatches = function(index) { // Domain prefixes to test. Tenant aliases are being set to ensure we don't wind up with // issues with tenant aliases being the string leaf keys of the index @@ -179,12 +176,12 @@ describe('Tenants', () => { }; /*! - * Ensure that the `index.conflict` function works as expected assuming all the standard - * entries that are inserted using `_createIndex()` - * - * @param {TenantEmailDomainIndex} index The index to test - * @throws {AssertionError} Thrown if any of the assertions fail - */ + * Ensure that the `index.conflict` function works as expected assuming all the standard + * entries that are inserted using `_createIndex()` + * + * @param {TenantEmailDomainIndex} index The index to test + * @throws {AssertionError} Thrown if any of the assertions fail + */ const _assertAllStandardConflicts = function(index) { // Domain prefixes to test. Tenant aliases are being set to ensure we don't wind up with // issues with tenant aliases being the string leaf keys of the index @@ -237,10 +234,10 @@ describe('Tenants', () => { }; /*! - * Create a test email domain index using the standard test entries - * - * @return {TenantEmailDomainIndex} The email domain index - */ + * Create a test email domain index using the standard test entries + * + * @return {TenantEmailDomainIndex} The email domain index + */ const _createIndex = function() { const index = new TenantsEmailDomainIndex(); @@ -359,28 +356,19 @@ describe('Tenants', () => { // Update the display name of the guest tenant const newDisplayName = TestsUtil.generateRandomText(1); - TenantsTestUtil.updateTenantAndWait( - globalAdminRestContext, - 'guest', - { displayName: newDisplayName }, - err => { - assert.ok(!err); + TenantsTestUtil.updateTenantAndWait(globalAdminRestContext, 'guest', { displayName: newDisplayName }, err => { + assert.ok(!err); - // Get the guest tenant again and ensure it has the updates - const expectedUpdatedGuestTenant = _.extend({}, guestTenant0, { - displayName: newDisplayName - }); - RestAPI.Tenants.getTenant( - globalAdminRestContext, - 'guest', - (err, updatedGuestTenant1) => { - assert.ok(!err); - assert.deepStrictEqual(updatedGuestTenant1, expectedUpdatedGuestTenant); - return callback(); - } - ); - } - ); + // Get the guest tenant again and ensure it has the updates + const expectedUpdatedGuestTenant = _.extend({}, guestTenant0, { + displayName: newDisplayName + }); + RestAPI.Tenants.getTenant(globalAdminRestContext, 'guest', (err, updatedGuestTenant1) => { + assert.ok(!err); + assert.deepStrictEqual(updatedGuestTenant1, expectedUpdatedGuestTenant); + return callback(); + }); + }); }); }); }); @@ -425,61 +413,32 @@ describe('Tenants', () => { */ it('verify tenants can be looked up through an email address', callback => { // Create tenants with a configured email domain - TestsUtil.setupMultiTenantPrivacyEntities( - (publicTenant0, publicTenant1, privateTenant0, privateTenant1) => { - // Both tenants should be returned - let emails = [ - publicTenant0.privateUser.user.email, - privateTenant1.privateUser.user.email - ]; - RestAPI.Tenants.getTenantsByEmailAddress( - publicTenant0.publicUser.restContext, - emails, - (err, tenants) => { - assert.ok(!err); - assert.strictEqual( - tenants[publicTenant0.privateUser.user.email].alias, - publicTenant0.tenant.alias - ); - assert.strictEqual( - tenants[privateTenant1.privateUser.user.email].alias, - privateTenant1.tenant.alias - ); + TestsUtil.setupMultiTenantPrivacyEntities((publicTenant0, publicTenant1, privateTenant0, privateTenant1) => { + // Both tenants should be returned + let emails = [publicTenant0.privateUser.user.email, privateTenant1.privateUser.user.email]; + RestAPI.Tenants.getTenantsByEmailAddress(publicTenant0.publicUser.restContext, emails, (err, tenants) => { + assert.ok(!err); + assert.strictEqual(tenants[publicTenant0.privateUser.user.email].alias, publicTenant0.tenant.alias); + assert.strictEqual(tenants[privateTenant1.privateUser.user.email].alias, privateTenant1.tenant.alias); - // An email that ends up on the guest tenant should - // return the guest tenancy - emails = ['an.email.ending.up@on.the.guest.tenancy']; - RestAPI.Tenants.getTenantsByEmailAddress( - publicTenant0.publicUser.restContext, - emails, - (err, tenants) => { - assert.ok(!err); - assert.ok(tenants[emails[0]].isGuestTenant); - - // A combination of both - emails = [ - 'an.email.ending.up@on.the.guest.tenancy', - publicTenant0.publicUser.user.email - ]; - RestAPI.Tenants.getTenantsByEmailAddress( - publicTenant0.publicUser.restContext, - emails, - (err, tenants) => { - assert.ok(!err); - assert.ok(tenants[emails[0]].isGuestTenant); - assert.strictEqual( - tenants[publicTenant0.publicUser.user.email].alias, - publicTenant0.tenant.alias - ); - return callback(); - } - ); - } - ); - } - ); - } - ); + // An email that ends up on the guest tenant should + // return the guest tenancy + emails = ['an.email.ending.up@on.the.guest.tenancy']; + RestAPI.Tenants.getTenantsByEmailAddress(publicTenant0.publicUser.restContext, emails, (err, tenants) => { + assert.ok(!err); + assert.ok(tenants[emails[0]].isGuestTenant); + + // A combination of both + emails = ['an.email.ending.up@on.the.guest.tenancy', publicTenant0.publicUser.user.email]; + RestAPI.Tenants.getTenantsByEmailAddress(publicTenant0.publicUser.restContext, emails, (err, tenants) => { + assert.ok(!err); + assert.ok(tenants[emails[0]].isGuestTenant); + assert.strictEqual(tenants[publicTenant0.publicUser.user.email].alias, publicTenant0.tenant.alias); + return callback(); + }); + }); + }); + }); }); }); @@ -523,10 +482,7 @@ describe('Tenants', () => { assert.strictEqual(tenants.camtest.host, 'cambridge.oae.com'); assert.ok(tenants[tenantAlias]); assert.strictEqual(tenants[tenantAlias].host, tenantHost.toLowerCase()); - assert.strictEqual( - tenants[tenantAlias].emailDomains[0], - tenantEmailDomain.toLowerCase() - ); + assert.strictEqual(tenants[tenantAlias].emailDomains[0], tenantEmailDomain.toLowerCase()); assert.strictEqual(_.keys(tenants).length, numTenants + 1); // Verify that the global admin tenant is not included @@ -580,10 +536,7 @@ describe('Tenants', () => { const tenantByEmailDomain = TenantsAPI.getTenantByEmail(tenantEmailDomain); assert.strictEqual(tenantByEmailDomain.alias, tenantAlias); assert.strictEqual(tenantByEmailDomain.host, tenantHost.toLowerCase()); - assert.strictEqual( - tenantByEmailDomain.emailDomains[0], - tenantEmailDomain.toLowerCase() - ); + assert.strictEqual(tenantByEmailDomain.emailDomains[0], tenantEmailDomain.toLowerCase()); return callback(); }); @@ -732,92 +685,56 @@ describe('Tenants', () => { assert.ok(gotTenant1); assert.strictEqual(gotTenant1.alias, tenant1Alias); assert.strictEqual(gotTenant1.host, tenant1Host.toLowerCase()); - assert.strictEqual( - gotTenant1.emailDomains[0], - tenant1Opts.emailDomains[0].toLowerCase() - ); + assert.strictEqual(gotTenant1.emailDomains[0], tenant1Opts.emailDomains[0].toLowerCase()); // Ensure we can get tenant 1 with an email address by an exact match - gotTenant1 = TenantsAPI.getTenantByEmail( - util.format('mrvisser@%s', tenant1Opts.emailDomains) - ); + gotTenant1 = TenantsAPI.getTenantByEmail(util.format('mrvisser@%s', tenant1Opts.emailDomains)); assert.ok(gotTenant1); assert.strictEqual(gotTenant1.alias, tenant1Alias); assert.strictEqual(gotTenant1.host, tenant1Host.toLowerCase()); - assert.strictEqual( - gotTenant1.emailDomains[0], - tenant1Opts.emailDomains[0].toLowerCase() - ); + assert.strictEqual(gotTenant1.emailDomains[0], tenant1Opts.emailDomains[0].toLowerCase()); // Ensure we can get tenant 1 by a valid host suffix - gotTenant1 = TenantsAPI.getTenantByEmail( - util.format('prefix.%s', tenant1Opts.emailDomains) - ); + gotTenant1 = TenantsAPI.getTenantByEmail(util.format('prefix.%s', tenant1Opts.emailDomains)); assert.ok(gotTenant1); assert.strictEqual(gotTenant1.alias, tenant1Alias); assert.strictEqual(gotTenant1.host, tenant1Host.toLowerCase()); - assert.strictEqual( - gotTenant1.emailDomains[0], - tenant1Opts.emailDomains[0].toLowerCase() - ); + assert.strictEqual(gotTenant1.emailDomains[0], tenant1Opts.emailDomains[0].toLowerCase()); // Ensure we can get tenant 1 by a valid host suffix in an email address - gotTenant1 = TenantsAPI.getTenantByEmail( - util.format('mrvisser@prefix.%s', tenant1Opts.emailDomains) - ); + gotTenant1 = TenantsAPI.getTenantByEmail(util.format('mrvisser@prefix.%s', tenant1Opts.emailDomains)); assert.ok(gotTenant1); assert.strictEqual(gotTenant1.alias, tenant1Alias); assert.strictEqual(gotTenant1.host, tenant1Host.toLowerCase()); - assert.strictEqual( - gotTenant1.emailDomains[0], - tenant1Opts.emailDomains[0].toLowerCase() - ); + assert.strictEqual(gotTenant1.emailDomains[0], tenant1Opts.emailDomains[0].toLowerCase()); // Ensure we can get tenant 2 by an exact match let gotTenant2 = TenantsAPI.getTenantByEmail(tenant2Opts.emailDomains[0]); assert.ok(tenant2); assert.strictEqual(gotTenant2.alias, tenant2Alias); assert.strictEqual(gotTenant2.host, tenant2Host.toLowerCase()); - assert.strictEqual( - gotTenant2.emailDomains[0], - tenant2Opts.emailDomains[0].toLowerCase() - ); + assert.strictEqual(gotTenant2.emailDomains[0], tenant2Opts.emailDomains[0].toLowerCase()); // Ensure we can get tenant 2 by an email address domain exact match - gotTenant2 = TenantsAPI.getTenantByEmail( - util.format('mrvisser@%s', tenant2Opts.emailDomains[0]) - ); + gotTenant2 = TenantsAPI.getTenantByEmail(util.format('mrvisser@%s', tenant2Opts.emailDomains[0])); assert.ok(tenant2); assert.strictEqual(gotTenant2.alias, tenant2Alias); assert.strictEqual(gotTenant2.host, tenant2Host.toLowerCase()); - assert.strictEqual( - gotTenant2.emailDomains[0], - tenant2Opts.emailDomains[0].toLowerCase() - ); + assert.strictEqual(gotTenant2.emailDomains[0], tenant2Opts.emailDomains[0].toLowerCase()); // Ensure we can get tenant 2 by a valid host suffix - gotTenant2 = TenantsAPI.getTenantByEmail( - util.format('prefix.%s', tenant2Opts.emailDomains[0]) - ); + gotTenant2 = TenantsAPI.getTenantByEmail(util.format('prefix.%s', tenant2Opts.emailDomains[0])); assert.ok(tenant2); assert.strictEqual(gotTenant2.alias, tenant2Alias); assert.strictEqual(gotTenant2.host, tenant2Host.toLowerCase()); - assert.strictEqual( - gotTenant2.emailDomains[0], - tenant2Opts.emailDomains[0].toLowerCase() - ); + assert.strictEqual(gotTenant2.emailDomains[0], tenant2Opts.emailDomains[0].toLowerCase()); // Ensure we can get tenant 2 by an email address with a valid host suffix - gotTenant2 = TenantsAPI.getTenantByEmail( - util.format('mrvisser@prefix.%s', tenant2Opts.emailDomains[0]) - ); + gotTenant2 = TenantsAPI.getTenantByEmail(util.format('mrvisser@prefix.%s', tenant2Opts.emailDomains[0])); assert.ok(tenant2); assert.strictEqual(gotTenant2.alias, tenant2Alias); assert.strictEqual(gotTenant2.host, tenant2Host.toLowerCase()); - assert.strictEqual( - gotTenant2.emailDomains[0], - tenant2Opts.emailDomains[0].toLowerCase() - ); + assert.strictEqual(gotTenant2.emailDomains[0], tenant2Opts.emailDomains[0].toLowerCase()); // Some subtle things that should fall back to the guest tenant: const shouldBeGuest = [ @@ -852,12 +769,7 @@ describe('Tenants', () => { // Some real-world cases with the cambridge tenant: const expectedTenantAlias = global.oaeTests.tenants.cam.alias; - const shouldMatchCambridge = [ - 'cam.ac.uk', - 'admin.cam.ac.uk', - 'sports.cam.ac.uk', - 'uis.cam.ac.uk' - ]; + const shouldMatchCambridge = ['cam.ac.uk', 'admin.cam.ac.uk', 'sports.cam.ac.uk', 'uis.cam.ac.uk']; const shouldMatchCambridgeEmailAddresses = _.map(shouldMatchCambridge, domain => { return util.format('%s@%s', TestsUtil.generateTestUserId(), domain); @@ -890,9 +802,7 @@ describe('Tenants', () => { assert.strictEqual(meObj.anon, true); // Get the me feed on a non-existing tenant - const anonymousNonExistingRestContext = TestsUtil.createTenantRestContext( - 'harvard.oae.com' - ); + const anonymousNonExistingRestContext = TestsUtil.createTenantRestContext('harvard.oae.com'); RestAPI.User.getMe(anonymousNonExistingRestContext, (err, meObj) => { assert.ok(err); assert.strictEqual(err.code, 418); @@ -913,10 +823,7 @@ describe('Tenants', () => { // global admin tenant _.each(TenantsAPI.getNonInteractingTenants(), tenant => { assert.ok( - tenant.isGlobalAdminServer || - !tenant.active || - tenant.deleted || - TenantsUtil.isPrivate(tenant.alias) + tenant.isGlobalAdminServer || !tenant.active || tenant.deleted || TenantsUtil.isPrivate(tenant.alias) ); }); @@ -936,14 +843,9 @@ describe('Tenants', () => { // Make the tenant private const makePrivateUpdate = { 'oae-tenants/tenantprivacy/tenantprivate': true }; - ConfigTestUtil.updateConfigAndWait( - globalAdminRestContext, - tenantAlias, - makePrivateUpdate, - err => { - assert.ok(!err); - } - ); + ConfigTestUtil.updateConfigAndWait(globalAdminRestContext, tenantAlias, makePrivateUpdate, err => { + assert.ok(!err); + }); TenantsAPI.emitter.once('cached', () => { // After the tenants have been recached, ensure the tenant now appears in the @@ -952,36 +854,25 @@ describe('Tenants', () => { // Make the tenant public again, ensure it gets removed from the list const makePublicUpdate = { 'oae-tenants/tenantprivacy/tenantprivate': false }; - ConfigTestUtil.updateConfigAndWait( - globalAdminRestContext, - tenantAlias, - makePublicUpdate, - err => { - assert.ok(!err); - } - ); + ConfigTestUtil.updateConfigAndWait(globalAdminRestContext, tenantAlias, makePublicUpdate, err => { + assert.ok(!err); + }); TenantsAPI.emitter.once('cached', () => { // After the tenants have been recached, ensure the tenant no longer appears // in the private tenants list - assert.ok( - !_.findWhere(TenantsAPI.getNonInteractingTenants(), { alias: tenantAlias }) - ); + assert.ok(!_.findWhere(TenantsAPI.getNonInteractingTenants(), { alias: tenantAlias })); // Disable the tenant and ensure it goes into the list of non-interacting // tenants TenantsTestUtil.stopTenantAndWait(globalAdminRestContext, tenantAlias, err => { assert.ok(!err); - assert.ok( - _.findWhere(TenantsAPI.getNonInteractingTenants(), { alias: tenantAlias }) - ); + assert.ok(_.findWhere(TenantsAPI.getNonInteractingTenants(), { alias: tenantAlias })); // Enable the tenant and ensure it comes back out of the list TenantsTestUtil.startTenantAndWait(globalAdminRestContext, tenantAlias, err => { assert.ok(!err); - assert.ok( - !_.findWhere(TenantsAPI.getNonInteractingTenants(), { alias: tenantAlias }) - ); + assert.ok(!_.findWhere(TenantsAPI.getNonInteractingTenants(), { alias: tenantAlias })); return callback(); }); }); @@ -1070,141 +961,117 @@ describe('Tenants', () => { const tenantHost = TenantsTestUtil.generateTestTenantHost(); // Try creating a tenant with no alias - TenantsTestUtil.createTenantAndWait( - globalAdminRestContext, - null, - 'AAR', - tenantHost, - null, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); + TenantsTestUtil.createTenantAndWait(globalAdminRestContext, null, 'AAR', tenantHost, null, err => { + assert.ok(err); + assert.strictEqual(err.code, 400); - // Try creating a tenant with an invalid alias, using spaces in the alias - TenantsTestUtil.createTenantAndWait( - globalAdminRestContext, - 'American Academic of Religion', - 'AAR', - tenantHost, - null, - err => { + // Try creating a tenant with an invalid alias, using spaces in the alias + TenantsTestUtil.createTenantAndWait( + globalAdminRestContext, + 'American Academic of Religion', + 'AAR', + tenantHost, + null, + err => { + assert.ok(err); + assert.strictEqual(err.code, 400); + + // Try creating a tenant with an invalid alias, using a colon in the alias + TenantsTestUtil.createTenantAndWait(globalAdminRestContext, 'aar:test', 'AAR', tenantHost, null, err => { assert.ok(err); assert.strictEqual(err.code, 400); - // Try creating a tenant with an invalid alias, using a colon in the alias + // Try creating a tenant with an alias that's already taken TenantsTestUtil.createTenantAndWait( globalAdminRestContext, - 'aar:test', - 'AAR', + 'camtest', + 'Cambridge University', tenantHost, null, err => { assert.ok(err); assert.strictEqual(err.code, 400); - // Try creating a tenant with an alias that's already taken + // Try creating a tenant with no displayName TenantsTestUtil.createTenantAndWait( globalAdminRestContext, - 'camtest', - 'Cambridge University', + tenantAlias, + null, tenantHost, null, err => { assert.ok(err); assert.strictEqual(err.code, 400); - // Try creating a tenant with no displayName + // Try creating a tenant with no base URL TenantsTestUtil.createTenantAndWait( globalAdminRestContext, tenantAlias, + 'AAR', null, - tenantHost, null, err => { assert.ok(err); assert.strictEqual(err.code, 400); - // Try creating a tenant with no base URL + // Try creating a tenant with an invalid hostname TenantsTestUtil.createTenantAndWait( globalAdminRestContext, tenantAlias, - 'AAR', - null, + 'Cambridge University', + 'not a valid hostname', null, err => { assert.ok(err); assert.strictEqual(err.code, 400); - // Try creating a tenant with an invalid hostname + // Try creating a tenant with a host name that's already taken TenantsTestUtil.createTenantAndWait( globalAdminRestContext, tenantAlias, 'Cambridge University', - 'not a valid hostname', + 'cambridge.oae.com', null, err => { assert.ok(err); assert.strictEqual(err.code, 400); - // Try creating a tenant with a host name that's already taken + // Try creating a tenant with an invalid country code TenantsTestUtil.createTenantAndWait( globalAdminRestContext, tenantAlias, 'Cambridge University', - 'cambridge.oae.com', - null, + tenantHost, + { countryCode: 'ZZ' }, err => { assert.ok(err); assert.strictEqual(err.code, 400); - // Try creating a tenant with an invalid country code - TenantsTestUtil.createTenantAndWait( - globalAdminRestContext, - tenantAlias, - 'Cambridge University', - tenantHost, - { countryCode: 'ZZ' }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); + // Verify that the tenant does not exist + const tenantRestContext = TestsUtil.createTenantRestContext(tenantHost); + RestAPI.Tenants.getTenant(tenantRestContext, null, (err, tenant) => { + assert.ok(err); + assert.strictEqual(err.code, 418); + assert.ok(!tenant); + + // Sanity check creating the tenant with our generated values and ensure the tenant exists after + TenantsTestUtil.createTenantAndWait( + globalAdminRestContext, + tenantAlias, + 'Cambridge University', + tenantHost, + { countryCode: 'CA' }, + err => { + assert.ok(!err); + RestAPI.Tenants.getTenant(tenantRestContext, null, (err, tenant) => { + assert.ok(!err); + assert.ok(tenant); - // Verify that the tenant does not exist - const tenantRestContext = TestsUtil.createTenantRestContext( - tenantHost - ); - RestAPI.Tenants.getTenant( - tenantRestContext, - null, - (err, tenant) => { - assert.ok(err); - assert.strictEqual(err.code, 418); - assert.ok(!tenant); - - // Sanity check creating the tenant with our generated values and ensure the tenant exists after - TenantsTestUtil.createTenantAndWait( - globalAdminRestContext, - tenantAlias, - 'Cambridge University', - tenantHost, - { countryCode: 'CA' }, - err => { - assert.ok(!err); - RestAPI.Tenants.getTenant( - tenantRestContext, - null, - (err, tenant) => { - assert.ok(!err); - assert.ok(tenant); - - return callback(); - } - ); - } - ); - } - ); - } - ); + return callback(); + }); + } + ); + }); } ); } @@ -1217,10 +1084,10 @@ describe('Tenants', () => { ); } ); - } - ); - } - ); + }); + } + ); + }); }); /** @@ -1320,68 +1187,40 @@ describe('Tenants', () => { let opts = { emailDomains: [emailDomain1, emailDomain2, emailDomain3] }; - TenantsTestUtil.createTenantAndWait( - globalAdminRestContext, - alias1, - alias1, - host1, - opts, - err => { - assert.ok(!err); + TenantsTestUtil.createTenantAndWait(globalAdminRestContext, alias1, alias1, host1, opts, err => { + assert.ok(!err); - // Creating a tenant where 1 email domain conflicts should fail - const alias2 = TenantsTestUtil.generateTestTenantAlias(); - const host2 = TenantsTestUtil.generateTestTenantHost(); + // Creating a tenant where 1 email domain conflicts should fail + const alias2 = TenantsTestUtil.generateTestTenantAlias(); + const host2 = TenantsTestUtil.generateTestTenantHost(); + opts = { + emailDomains: [emailDomain3, emailDomain4, emailDomain5] + }; + TenantsTestUtil.createTenantAndWait(globalAdminRestContext, alias2, alias2, host2, opts, err => { + assert.ok(err); + assert.strictEqual(err.code, 400); + + // Creating a tenant where multiple email domains conflict should fail opts = { - emailDomains: [emailDomain3, emailDomain4, emailDomain5] + emailDomains: [emailDomain2, emailDomain3, emailDomain5] }; - TenantsTestUtil.createTenantAndWait( - globalAdminRestContext, - alias2, - alias2, - host2, - opts, - err => { + TenantsTestUtil.createTenantAndWait(globalAdminRestContext, alias2, alias2, host2, opts, err => { + assert.ok(err); + assert.strictEqual(err.code, 400); + + // Creating a tenant where 1 email domain suffix conflicts should fail + opts = { + emailDomains: [emailDomain4, emailDomain5, commonTld] + }; + TenantsTestUtil.createTenantAndWait(globalAdminRestContext, alias2, alias2, host2, opts, err => { assert.ok(err); assert.strictEqual(err.code, 400); - // Creating a tenant where multiple email domains conflict should fail - opts = { - emailDomains: [emailDomain2, emailDomain3, emailDomain5] - }; - TenantsTestUtil.createTenantAndWait( - globalAdminRestContext, - alias2, - alias2, - host2, - opts, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); - - // Creating a tenant where 1 email domain suffix conflicts should fail - opts = { - emailDomains: [emailDomain4, emailDomain5, commonTld] - }; - TenantsTestUtil.createTenantAndWait( - globalAdminRestContext, - alias2, - alias2, - host2, - opts, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); - - return callback(); - } - ); - } - ); - } - ); - } - ); + return callback(); + }); + }); + }); + }); }); /** @@ -1493,26 +1332,19 @@ describe('Tenants', () => { * Test that verifies that a tenant cannot be created with a duplicate alias */ it('verify create tenant duplicate alias', callback => { - TenantsTestUtil.createTenantAndWait( - globalAdminRestContext, - 'camtest', - 'AAR', - 'camtest.oae.com', - null, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); + TenantsTestUtil.createTenantAndWait(globalAdminRestContext, 'camtest', 'AAR', 'camtest.oae.com', null, err => { + assert.ok(err); + assert.strictEqual(err.code, 400); - // Verify that the existing tenant is still running - RestAPI.Tenants.getTenant(anonymousCamRestContext, null, (err, tenant) => { - assert.ok(!err); - assert.ok(tenant); - assert.strictEqual(tenant.alias, 'camtest'); - assert.strictEqual(tenant.host, 'cambridge.oae.com'); - callback(); - }); - } - ); + // Verify that the existing tenant is still running + RestAPI.Tenants.getTenant(anonymousCamRestContext, null, (err, tenant) => { + assert.ok(!err); + assert.ok(tenant); + assert.strictEqual(tenant.alias, 'camtest'); + assert.strictEqual(tenant.host, 'cambridge.oae.com'); + callback(); + }); + }); }); /** @@ -1546,29 +1378,22 @@ describe('Tenants', () => { */ it('verify creating a tenant with the Shibboleth SP host as hostname is not allowed', callback => { const spHost = ShibbolethAPI.getSPHost(); - TenantsTestUtil.createTenantAndWait( - globalAdminRestContext, - Math.random(), - 'bladiebla', - spHost, - null, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); - TenantsTestUtil.createTenantAndWait( - globalAdminRestContext, - Math.random(), - 'bladiebla', - spHost.toUpperCase(), - null, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); - return callback(); - } - ); - } - ); + TenantsTestUtil.createTenantAndWait(globalAdminRestContext, Math.random(), 'bladiebla', spHost, null, err => { + assert.ok(err); + assert.strictEqual(err.code, 400); + TenantsTestUtil.createTenantAndWait( + globalAdminRestContext, + Math.random(), + 'bladiebla', + spHost.toUpperCase(), + null, + err => { + assert.ok(err); + assert.strictEqual(err.code, 400); + return callback(); + } + ); + }); }); /** @@ -1579,31 +1404,27 @@ describe('Tenants', () => { it('verify creating a tenant with a dash in the alias does not break authentication strategy', callback => { const tenantHost = TenantsTestUtil.generateTestTenantHost(); const tenantAlias = 'test-with-dash'; - TestsUtil.createTenantWithAdmin( - 'alias-with-dash', - tenantHost, - (err, tenant, tenantAdminRestContext) => { - assert.ok(!err); - TestsUtil.generateTestUsers(tenantAdminRestContext, 1, (err, users, mrvisser) => { - // Ensure the tenant admin's me object properly represents the authentication - // strategy - RestAPI.User.getMe(tenantAdminRestContext, (err, me) => { + TestsUtil.createTenantWithAdmin('alias-with-dash', tenantHost, (err, tenant, tenantAdminRestContext) => { + assert.ok(!err); + TestsUtil.generateTestUsers(tenantAdminRestContext, 1, (err, users, mrvisser) => { + // Ensure the tenant admin's me object properly represents the authentication + // strategy + RestAPI.User.getMe(tenantAdminRestContext, (err, me) => { + assert.ok(!err); + assert.strictEqual(me.isTenantAdmin, true); + assert.strictEqual(me.authenticationStrategy, 'local'); + + // Ensure the regular user's me object properly represents the + // authentication strategy + RestAPI.User.getMe(mrvisser.restContext, (err, me) => { assert.ok(!err); - assert.strictEqual(me.isTenantAdmin, true); + assert.ok(!me.anon); assert.strictEqual(me.authenticationStrategy, 'local'); - - // Ensure the regular user's me object properly represents the - // authentication strategy - RestAPI.User.getMe(mrvisser.restContext, (err, me) => { - assert.ok(!err); - assert.ok(!me.anon); - assert.strictEqual(me.authenticationStrategy, 'local'); - return callback(); - }); + return callback(); }); }); - } - ); + }); + }); }); /** @@ -1613,80 +1434,72 @@ describe('Tenants', () => { // Create a new tenant const tenantHost = TenantsTestUtil.generateTestTenantHost(); const tenantAlias = TenantsTestUtil.generateTestTenantAlias(); - TestsUtil.createTenantWithAdmin( - tenantAlias, - tenantHost, - (err, testTenant, tenantAdminRestContext) => { - const restContext = TestsUtil.createTenantRestContext(testTenant.host); + TestsUtil.createTenantWithAdmin(tenantAlias, tenantHost, (err, testTenant, tenantAdminRestContext) => { + const restContext = TestsUtil.createTenantRestContext(testTenant.host); - // Verify that the tenant is running - RestAPI.Tenants.getTenant(restContext, null, (err, tenant) => { - assert.ok(!err); - assert.ok(tenant); - assert.strictEqual(tenant.alias, testTenant.alias); + // Verify that the tenant is running + RestAPI.Tenants.getTenant(restContext, null, (err, tenant) => { + assert.ok(!err); + assert.ok(tenant); + assert.strictEqual(tenant.alias, testTenant.alias); - // Verify it's in the list of running tenant aliases - assert.ok(TenantsAPI.getTenants(true)[testTenant.alias]); + // Verify it's in the list of running tenant aliases + assert.ok(TenantsAPI.getTenants(true)[testTenant.alias]); - // Create users so we can verify they get disabled too - TestsUtil.generateTestUsers( - tenantAdminRestContext, - 3, - (err, users, user1, user2, user3) => { - assert.ok(!err); + // Create users so we can verify they get disabled too + TestsUtil.generateTestUsers(tenantAdminRestContext, 3, (err, users, user1, user2, user3) => { + assert.ok(!err); - // Stop the tenant - TenantsTestUtil.stopTenantAndWait(globalAdminRestContext, testTenant.alias, err => { - assert.ok(!err); + // Stop the tenant + TenantsTestUtil.stopTenantAndWait(globalAdminRestContext, testTenant.alias, err => { + assert.ok(!err); - // Verify that the tenant is no longer running - RestAPI.Tenants.getTenant(restContext, null, (err, tenant) => { - assert.ok(err); - assert.strictEqual(err.code, 503); - assert.ok(!tenant); + // Verify that the tenant is no longer running + RestAPI.Tenants.getTenant(restContext, null, (err, tenant) => { + assert.ok(err); + assert.strictEqual(err.code, 503); + assert.ok(!tenant); - // Verify it is no longer in the list of running tenant aliases - assert.ok(!TenantsAPI.getTenants(true)[testTenant.alias]); - // Verify it is in the list of disabled tenant aliases - assert.ok(TenantsAPI.getTenants()[testTenant.alias]); - assert.strictEqual(TenantsAPI.getTenants()[testTenant.alias].active, false); + // Verify it is no longer in the list of running tenant aliases + assert.ok(!TenantsAPI.getTenants(true)[testTenant.alias]); + // Verify it is in the list of disabled tenant aliases + assert.ok(TenantsAPI.getTenants()[testTenant.alias]); + assert.strictEqual(TenantsAPI.getTenants()[testTenant.alias].active, false); - // Verify that it's still part of the all tenants feed - RestAPI.Tenants.getTenants(globalAdminRestContext, (err, tenants) => { + // Verify that it's still part of the all tenants feed + RestAPI.Tenants.getTenants(globalAdminRestContext, (err, tenants) => { + assert.ok(!err); + assert.ok(tenants); + assert.ok(tenants[testTenant.alias]); + assert.strictEqual(tenants[testTenant.alias].host, testTenant.host); + assert.strictEqual(tenants[testTenant.alias].active, false); + + // Verify the users got disabled too + PrincipalsDAO.getPrincipal(user1.user.id, (err, principal1) => { + assert.ok(!err); + assert.ok(!_.isUndefined(principal1.deleted)); + assert.ok(principal1.deleted > 0); + + PrincipalsDAO.getPrincipal(user2.user.id, (err, principal2) => { assert.ok(!err); - assert.ok(tenants); - assert.ok(tenants[testTenant.alias]); - assert.strictEqual(tenants[testTenant.alias].host, testTenant.host); - assert.strictEqual(tenants[testTenant.alias].active, false); + assert.ok(!_.isUndefined(principal2.deleted)); + assert.ok(principal2.deleted > 0); - // Verify the users got disabled too - PrincipalsDAO.getPrincipal(user1.user.id, (err, principal1) => { + PrincipalsDAO.getPrincipal(user3.user.id, (err, principal3) => { assert.ok(!err); - assert.ok(!_.isUndefined(principal1.deleted)); - assert.ok(principal1.deleted > 0); + assert.ok(!_.isUndefined(principal3.deleted)); + assert.ok(principal3.deleted > 0); - PrincipalsDAO.getPrincipal(user2.user.id, (err, principal2) => { - assert.ok(!err); - assert.ok(!_.isUndefined(principal2.deleted)); - assert.ok(principal2.deleted > 0); - - PrincipalsDAO.getPrincipal(user3.user.id, (err, principal3) => { - assert.ok(!err); - assert.ok(!_.isUndefined(principal3.deleted)); - assert.ok(principal3.deleted > 0); - - return callback(); - }); - }); + return callback(); }); }); }); }); - } - ); + }); + }); }); - } - ); + }); + }); }); /** @@ -1736,66 +1549,54 @@ describe('Tenants', () => { // TenantsTestUtil.generateTestTenants(globalAdminRestContext, 1, function(testTenant) { const tenantHost = TenantsTestUtil.generateTestTenantHost(); const tenantAlias = TenantsTestUtil.generateTestTenantAlias(); - TestsUtil.createTenantWithAdmin( - tenantAlias, - tenantHost, - (err, testTenant, tenantAdminRestContext) => { - // Create users so we can verify they get disabled and then re-enabled too - TestsUtil.generateTestUsers( - tenantAdminRestContext, - 3, - (err, users, user1, user2, user3) => { - assert.ok(!err); + TestsUtil.createTenantWithAdmin(tenantAlias, tenantHost, (err, testTenant, tenantAdminRestContext) => { + // Create users so we can verify they get disabled and then re-enabled too + TestsUtil.generateTestUsers(tenantAdminRestContext, 3, (err, users, user1, user2, user3) => { + assert.ok(!err); - // Stop the tenant - TenantsTestUtil.stopTenantAndWait(globalAdminRestContext, testTenant.alias, err => { + // Stop the tenant + TenantsTestUtil.stopTenantAndWait(globalAdminRestContext, testTenant.alias, err => { + assert.ok(!err); + + // Verify that the tenant has indeed stopped + const restContext = TestsUtil.createTenantRestContext(testTenant.host); + RestAPI.Tenants.getTenant(restContext, null, (err, tenant) => { + assert.ok(err); + assert.strictEqual(err.code, 503); + + // Now start the tenant + TenantsTestUtil.startTenantAndWait(globalAdminRestContext, testTenant.alias, err => { assert.ok(!err); - // Verify that the tenant has indeed stopped - const restContext = TestsUtil.createTenantRestContext(testTenant.host); + // Verify that the tenant has indeed been started RestAPI.Tenants.getTenant(restContext, null, (err, tenant) => { - assert.ok(err); - assert.strictEqual(err.code, 503); + assert.ok(!err); + assert.strictEqual(tenant.alias, testTenant.alias); + assert.strictEqual(tenant.active, true); - // Now start the tenant - TenantsTestUtil.startTenantAndWait( - globalAdminRestContext, - testTenant.alias, - err => { + // Verify the users got re-enabled too + PrincipalsDAO.getPrincipal(user1.user.id, (err, principal1) => { + assert.ok(!err); + assert.ok(_.isUndefined(principal1.deleted)); + + PrincipalsDAO.getPrincipal(user2.user.id, (err, principal2) => { assert.ok(!err); + assert.ok(_.isUndefined(principal2.deleted)); - // Verify that the tenant has indeed been started - RestAPI.Tenants.getTenant(restContext, null, (err, tenant) => { + PrincipalsDAO.getPrincipal(user3.user.id, (err, principal3) => { assert.ok(!err); - assert.strictEqual(tenant.alias, testTenant.alias); - assert.strictEqual(tenant.active, true); - - // Verify the users got re-enabled too - PrincipalsDAO.getPrincipal(user1.user.id, (err, principal1) => { - assert.ok(!err); - assert.ok(_.isUndefined(principal1.deleted)); - - PrincipalsDAO.getPrincipal(user2.user.id, (err, principal2) => { - assert.ok(!err); - assert.ok(_.isUndefined(principal2.deleted)); + assert.ok(_.isUndefined(principal3.deleted)); - PrincipalsDAO.getPrincipal(user3.user.id, (err, principal3) => { - assert.ok(!err); - assert.ok(_.isUndefined(principal3.deleted)); - - return callback(); - }); - }); - }); + return callback(); }); - } - ); + }); + }); }); }); - } - ); - } - ); + }); + }); + }); + }); }); /** @@ -1863,105 +1664,91 @@ describe('Tenants', () => { assert.strictEqual(err.code, 401); // Try to update the tenant's host as an anonymous user on the global admin tenant - RestAPI.Tenants.updateTenant( - anonymousGlobalRestContext, - 'camtest', - { host: 'newcamtest.oae.com' }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 401); + RestAPI.Tenants.updateTenant(anonymousGlobalRestContext, 'camtest', { host: 'newcamtest.oae.com' }, err => { + assert.ok(err); + assert.strictEqual(err.code, 401); - // Try to update tenant's display name and host as an anonymous user on the global admin tenant - RestAPI.Tenants.updateTenant( - anonymousGlobalRestContext, - 'camtest', - { displayName: 'Anglia Ruskin University', host: 'newcamtest.oae.com' }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 401); + // Try to update tenant's display name and host as an anonymous user on the global admin tenant + RestAPI.Tenants.updateTenant( + anonymousGlobalRestContext, + 'camtest', + { displayName: 'Anglia Ruskin University', host: 'newcamtest.oae.com' }, + err => { + assert.ok(err); + assert.strictEqual(err.code, 401); - // Try to update the tenant's display name as an anonymous user on a user tenant - RestAPI.Tenants.updateTenant( - anonymousCamRestContext, - null, - { displayName: 'Anglia Ruskin University' }, - err => { + // Try to update the tenant's display name as an anonymous user on a user tenant + RestAPI.Tenants.updateTenant( + anonymousCamRestContext, + null, + { displayName: 'Anglia Ruskin University' }, + err => { + assert.ok(err); + assert.strictEqual(err.code, 401); + + // Try to update the tenant's host as an anonymous user on a user tenant + RestAPI.Tenants.updateTenant(anonymousCamRestContext, null, { host: 'newcamtest.oae.com' }, err => { assert.ok(err); assert.strictEqual(err.code, 401); - // Try to update the tenant's host as an anonymous user on a user tenant + // Try to update tenant's display name and host as an anonymous user on a user tenant RestAPI.Tenants.updateTenant( anonymousCamRestContext, null, - { host: 'newcamtest.oae.com' }, + { displayName: 'Anglia Ruskin University', host: 'newcamtest.oae.com' }, err => { assert.ok(err); assert.strictEqual(err.code, 401); - // Try to update tenant's display name and host as an anonymous user on a user tenant - RestAPI.Tenants.updateTenant( - anonymousCamRestContext, - null, - { displayName: 'Anglia Ruskin University', host: 'newcamtest.oae.com' }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 401); - - // Create a regular non-admin user - TestsUtil.generateTestUsers( - camAdminRestContext, - 1, - (err, users, john) => { - assert.ok(!err); - - // Try to update the tenant's display name as a non-admin user on a user tenant - RestAPI.Tenants.updateTenant( - john.restContext, - null, - { displayName: 'Anglia Ruskin University' }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 401); - - // Try to update the tenant's host as a non-admin user on a user tenant - RestAPI.Tenants.updateTenant( - john.restContext, - null, - { host: 'newcamtest.oae.com' }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 401); - - // Try to update tenant's display name and host as a non-admin user on a user tenant - RestAPI.Tenants.updateTenant( - john.restContext, - null, - { - displayName: 'Anglia Ruskin University', - host: 'newcamtest.oae.com' - }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 401); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); + // Create a regular non-admin user + TestsUtil.generateTestUsers(camAdminRestContext, 1, (err, users, john) => { + assert.ok(!err); + + // Try to update the tenant's display name as a non-admin user on a user tenant + RestAPI.Tenants.updateTenant( + john.restContext, + null, + { displayName: 'Anglia Ruskin University' }, + err => { + assert.ok(err); + assert.strictEqual(err.code, 401); + + // Try to update the tenant's host as a non-admin user on a user tenant + RestAPI.Tenants.updateTenant( + john.restContext, + null, + { host: 'newcamtest.oae.com' }, + err => { + assert.ok(err); + assert.strictEqual(err.code, 401); + + // Try to update tenant's display name and host as a non-admin user on a user tenant + RestAPI.Tenants.updateTenant( + john.restContext, + null, + { + displayName: 'Anglia Ruskin University', + host: 'newcamtest.oae.com' + }, + err => { + assert.ok(err); + assert.strictEqual(err.code, 401); + return callback(); + } + ); + } + ); + } + ); + }); } ); - } - ); - } - ); - } - ); + }); + } + ); + } + ); + }); } ); }); @@ -1975,82 +1762,67 @@ describe('Tenants', () => { assert.ok(err); assert.strictEqual(err.code, 400); // Verify update with an invalid property - RestAPI.Tenants.updateTenant( - globalAdminRestContext, - 'camtest', - { alias: 'foobar' }, - err => { + RestAPI.Tenants.updateTenant(globalAdminRestContext, 'camtest', { alias: 'foobar' }, err => { + assert.ok(err); + assert.strictEqual(err.code, 400); + + // Verify through a user tenant + RestAPI.Tenants.updateTenant(camAdminRestContext, null, null, err => { assert.ok(err); assert.strictEqual(err.code, 400); - - // Verify through a user tenant - RestAPI.Tenants.updateTenant(camAdminRestContext, null, null, err => { + // Verify update with an invalid property + RestAPI.Tenants.updateTenant(camAdminRestContext, null, { alias: 'foobar' }, err => { assert.ok(err); assert.strictEqual(err.code, 400); - // Verify update with an invalid property - RestAPI.Tenants.updateTenant(camAdminRestContext, null, { alias: 'foobar' }, err => { + + // Verify updating to host that's already used + RestAPI.Tenants.updateTenant(camAdminRestContext, null, { host: 'caMBriDGe.oae.com' }, err => { assert.ok(err); assert.strictEqual(err.code, 400); - // Verify updating to host that's already used + // Verify updating with an invalid host RestAPI.Tenants.updateTenant( - camAdminRestContext, - null, - { host: 'caMBriDGe.oae.com' }, + globalAdminRestContext, + 'camtest', + { host: 'an invalid hostname' }, err => { assert.ok(err); assert.strictEqual(err.code, 400); - // Verify updating with an invalid host + // Verify updating with an invalid email domains RestAPI.Tenants.updateTenant( globalAdminRestContext, 'camtest', - { host: 'an invalid hostname' }, + { emailDomains: ['an invalid email domain'] }, err => { assert.ok(err); assert.strictEqual(err.code, 400); - // Verify updating with an invalid email domains + // Verify updating with a set of badly serialized email domains RestAPI.Tenants.updateTenant( globalAdminRestContext, 'camtest', - { emailDomains: ['an invalid email domain'] }, + { emailDomains: ['foo.test.com,bar.test.com'] }, err => { assert.ok(err); assert.strictEqual(err.code, 400); - // Verify updating with a set of badly serialized email domains + // Verify updating a non-existing tenant fails RestAPI.Tenants.updateTenant( globalAdminRestContext, - 'camtest', - { emailDomains: ['foo.test.com,bar.test.com'] }, + TestsUtil.generateRandomText(), + { displayName: "I'm totally legit..." }, err => { assert.ok(err); - assert.strictEqual(err.code, 400); - - // Verify updating a non-existing tenant fails - RestAPI.Tenants.updateTenant( - globalAdminRestContext, - TestsUtil.generateRandomText(), - { displayName: "I'm totally legit..." }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 404); + assert.strictEqual(err.code, 404); - // Verify updating country code to an invalid value - RestAPI.Tenants.updateTenant( - camAdminRestContext, - null, - { countryCode: 'ZZ' }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); + // Verify updating country code to an invalid value + RestAPI.Tenants.updateTenant(camAdminRestContext, null, { countryCode: 'ZZ' }, err => { + assert.ok(err); + assert.strictEqual(err.code, 400); - return callback(); - } - ); - } - ); + return callback(); + }); } ); } @@ -2061,8 +1833,8 @@ describe('Tenants', () => { ); }); }); - } - ); + }); + }); }); }); @@ -2098,82 +1870,56 @@ describe('Tenants', () => { update = { emailDomains: [emailDomain1] }; - TenantsTestUtil.updateTenantAndWait( - globalAdminRestContext, - tenant.alias, - update, - err => { + TenantsTestUtil.updateTenantAndWait(globalAdminRestContext, tenant.alias, update, err => { + assert.ok(!err); + RestAPI.Tenants.getTenant(globalAdminRestContext, tenant.alias, (err, tenant) => { assert.ok(!err); - RestAPI.Tenants.getTenant(globalAdminRestContext, tenant.alias, (err, tenant) => { - assert.ok(!err); - assert.deepStrictEqual(tenant.emailDomains, update.emailDomains); - - // Ensure the other email domains can now be used to create a second tenant - const alias2 = TenantsTestUtil.generateTestTenantAlias(); - const host2 = TenantsTestUtil.generateTestTenantHost(); - const opts = { - emailDomains: [emailDomain2, emailDomain3].sort() - }; - TenantsTestUtil.createTenantAndWait( - globalAdminRestContext, - alias2, - alias2, - host2, - opts, - (err, secondTenant) => { + assert.deepStrictEqual(tenant.emailDomains, update.emailDomains); + + // Ensure the other email domains can now be used to create a second tenant + const alias2 = TenantsTestUtil.generateTestTenantAlias(); + const host2 = TenantsTestUtil.generateTestTenantHost(); + const opts = { + emailDomains: [emailDomain2, emailDomain3].sort() + }; + TenantsTestUtil.createTenantAndWait( + globalAdminRestContext, + alias2, + alias2, + host2, + opts, + (err, secondTenant) => { + assert.ok(!err); + assert.deepStrictEqual(secondTenant.emailDomains.sort(), opts.emailDomains); + + // Unset the email domains + update = { + emailDomains: '' + }; + TenantsTestUtil.updateTenantAndWait(globalAdminRestContext, tenant.alias, update, err => { assert.ok(!err); - assert.deepStrictEqual(secondTenant.emailDomains.sort(), opts.emailDomains); + RestAPI.Tenants.getTenant(globalAdminRestContext, tenant.alias, (err, tenant) => { + assert.ok(!err); + assert.strictEqual(tenant.emailDomains.length, 0); - // Unset the email domains - update = { - emailDomains: '' - }; - TenantsTestUtil.updateTenantAndWait( - globalAdminRestContext, - tenant.alias, - update, - err => { + // The unset email domain can now be added to another tenant + update = { + emailDomains: [emailDomain1, emailDomain2, emailDomain3].sort() + }; + TenantsTestUtil.updateTenantAndWait(globalAdminRestContext, secondTenant.alias, update, err => { assert.ok(!err); - RestAPI.Tenants.getTenant( - globalAdminRestContext, - tenant.alias, - (err, tenant) => { - assert.ok(!err); - assert.strictEqual(tenant.emailDomains.length, 0); - - // The unset email domain can now be added to another tenant - update = { - emailDomains: [emailDomain1, emailDomain2, emailDomain3].sort() - }; - TenantsTestUtil.updateTenantAndWait( - globalAdminRestContext, - secondTenant.alias, - update, - err => { - assert.ok(!err); - RestAPI.Tenants.getTenant( - globalAdminRestContext, - secondTenant.alias, - (err, secondTenant) => { - assert.ok(!err); - assert.deepStrictEqual( - secondTenant.emailDomains.sort(), - update.emailDomains - ); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - }); - } - ); + RestAPI.Tenants.getTenant(globalAdminRestContext, secondTenant.alias, (err, secondTenant) => { + assert.ok(!err); + assert.deepStrictEqual(secondTenant.emailDomains.sort(), update.emailDomains); + return callback(); + }); + }); + }); + }); + } + ); + }); + }); }); }); }); @@ -2214,118 +1960,109 @@ describe('Tenants', () => { const emailDomain2SuffixConflict2 = util.format('a.%s', emailDomain2); // Ensure only global admin can update email domain - RestAPI.Tenants.updateTenant( - tenantAdminRestContext, - null, - { emailDomains: [emailDomain1] }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 401); - TenantsTestUtil.updateTenantAndWait( - globalAdminRestContext, - alias1, - { emailDomains: [emailDomain1] }, - err => { - assert.ok(!err); + RestAPI.Tenants.updateTenant(tenantAdminRestContext, null, { emailDomains: [emailDomain1] }, err => { + assert.ok(err); + assert.strictEqual(err.code, 401); + TenantsTestUtil.updateTenantAndWait( + globalAdminRestContext, + alias1, + { emailDomains: [emailDomain1] }, + err => { + assert.ok(!err); - // Ensure we can't set an email domain for tenant 2 that conflicts with tenant1 - RestAPI.Tenants.updateTenant( - globalAdminRestContext, - alias2, - { emailDomains: [emailDomain1ExactConflict] }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); - RestAPI.Tenants.updateTenant( - globalAdminRestContext, - alias2, - { emailDomains: [emailDomain1SuffixConflict1] }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); - RestAPI.Tenants.updateTenant( - globalAdminRestContext, - alias2, - { emailDomains: [emailDomain1SuffixConflict2] }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); + // Ensure we can't set an email domain for tenant 2 that conflicts with tenant1 + RestAPI.Tenants.updateTenant( + globalAdminRestContext, + alias2, + { emailDomains: [emailDomain1ExactConflict] }, + err => { + assert.ok(err); + assert.strictEqual(err.code, 400); + RestAPI.Tenants.updateTenant( + globalAdminRestContext, + alias2, + { emailDomains: [emailDomain1SuffixConflict1] }, + err => { + assert.ok(err); + assert.strictEqual(err.code, 400); + RestAPI.Tenants.updateTenant( + globalAdminRestContext, + alias2, + { emailDomains: [emailDomain1SuffixConflict2] }, + err => { + assert.ok(err); + assert.strictEqual(err.code, 400); - // Update tenant 2 to an email domain that doesn't conflict - TenantsTestUtil.updateTenantAndWait( - globalAdminRestContext, - alias2, - { emailDomains: [emailDomain2] }, - err => { - assert.ok(!err); + // Update tenant 2 to an email domain that doesn't conflict + TenantsTestUtil.updateTenantAndWait( + globalAdminRestContext, + alias2, + { emailDomains: [emailDomain2] }, + err => { + assert.ok(!err); - // Ensure we can update tenant 1's email domain to conflict with itself - TenantsTestUtil.updateTenantAndWait( - globalAdminRestContext, - alias1, - { emailDomains: [emailDomain1SuffixConflict1] }, - err => { - assert.ok(!err); - - // Ensure we can't update tenant1's email domain to conflict with tenant 2's - RestAPI.Tenants.updateTenant( - globalAdminRestContext, - alias1, - { emailDomains: [emailDomain2SuffixConflict1] }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); - RestAPI.Tenants.updateTenant( - globalAdminRestContext, - alias1, - { emailDomains: [emailDomain2SuffixConflict2] }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); - - // Sanity check that the email domains are now set to what we would expect + // Ensure we can update tenant 1's email domain to conflict with itself + TenantsTestUtil.updateTenantAndWait( + globalAdminRestContext, + alias1, + { emailDomains: [emailDomain1SuffixConflict1] }, + err => { + assert.ok(!err); + + // Ensure we can't update tenant1's email domain to conflict with tenant 2's + RestAPI.Tenants.updateTenant( + globalAdminRestContext, + alias1, + { emailDomains: [emailDomain2SuffixConflict1] }, + err => { + assert.ok(err); + assert.strictEqual(err.code, 400); + RestAPI.Tenants.updateTenant( + globalAdminRestContext, + alias1, + { emailDomains: [emailDomain2SuffixConflict2] }, + err => { + assert.ok(err); + assert.strictEqual(err.code, 400); + + // Sanity check that the email domains are now set to what we would expect + RestAPI.Tenants.getTenant(tenantAdminRestContext, null, (err, tenant) => { + assert.ok(!err); + assert.strictEqual( + tenant.emailDomains[0], + emailDomain1SuffixConflict1.toLowerCase() + ); RestAPI.Tenants.getTenant( - tenantAdminRestContext, - null, + globalAdminRestContext, + alias2, (err, tenant) => { assert.ok(!err); assert.strictEqual( tenant.emailDomains[0], - emailDomain1SuffixConflict1.toLowerCase() - ); - RestAPI.Tenants.getTenant( - globalAdminRestContext, - alias2, - (err, tenant) => { - assert.ok(!err); - assert.strictEqual( - tenant.emailDomains[0], - emailDomain2.toLowerCase() - ); - - return callback(); - } + emailDomain2.toLowerCase() ); + + return callback(); } ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); + }); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); }); }); }); @@ -2358,261 +2095,195 @@ describe('Tenants', () => { assert.strictEqual(tenant.displayName, 'Anglia Ruskin University'); // Update the tenant display name as the tenant admin - TenantsTestUtil.updateTenantAndWait( - camAdminRestContext, - null, - { displayName: 'Queens College' }, - err => { + TenantsTestUtil.updateTenantAndWait(camAdminRestContext, null, { displayName: 'Queens College' }, err => { + assert.ok(!err); + + // Check if the update was successful + RestAPI.Tenants.getTenant(camAdminRestContext, null, (err, tenant) => { assert.ok(!err); + assert.ok(tenant); + assert.strictEqual(tenant.alias, 'camtest'); + assert.strictEqual(tenant.host, 'cambridge.oae.com'); + assert.strictEqual(tenant.displayName, 'Queens College'); - // Check if the update was successful - RestAPI.Tenants.getTenant(camAdminRestContext, null, (err, tenant) => { + // Update the tenant host as the global admin + TenantsTestUtil.updateTenantAndWait(globalAdminRestContext, 'camtest', { host: tenant1Host }, err => { assert.ok(!err); - assert.ok(tenant); - assert.strictEqual(tenant.alias, 'camtest'); - assert.strictEqual(tenant.host, 'cambridge.oae.com'); - assert.strictEqual(tenant.displayName, 'Queens College'); - // Update the tenant host as the global admin - TenantsTestUtil.updateTenantAndWait( - globalAdminRestContext, - 'camtest', - { host: tenant1Host }, - err => { - assert.ok(!err); + // Check if the update was successful. + // The old host name should no longer be accepting requests + RestAPI.Tenants.getTenant(camAdminRestContext, null, (err, tenant) => { + assert.ok(err); + assert.strictEqual(err.code, 418); - // Check if the update was successful. - // The old host name should no longer be accepting requests - RestAPI.Tenants.getTenant(camAdminRestContext, null, (err, tenant) => { - assert.ok(err); - assert.strictEqual(err.code, 418); + // The new host name should now be responding to requests + const tenant1AdminRestContext = TestsUtil.createTenantAdminRestContext(tenant1Host); + RestAPI.Tenants.getTenant(tenant1AdminRestContext, null, (err, tenant) => { + assert.ok(!err); + assert.ok(tenant); + assert.strictEqual(tenant.alias, 'camtest'); + assert.strictEqual(tenant.host, tenant1Host.toLowerCase()); + assert.strictEqual(tenant.displayName, 'Queens College'); - // The new host name should now be responding to requests - const tenant1AdminRestContext = TestsUtil.createTenantAdminRestContext( - tenant1Host - ); - RestAPI.Tenants.getTenant(tenant1AdminRestContext, null, (err, tenant) => { + // Update the tenant host to have uppercase characters + TenantsTestUtil.updateTenantAndWait( + globalAdminRestContext, + 'camtest', + { host: tenant2Host.toUpperCase() }, + err => { assert.ok(!err); - assert.ok(tenant); - assert.strictEqual(tenant.alias, 'camtest'); - assert.strictEqual(tenant.host, tenant1Host.toLowerCase()); - assert.strictEqual(tenant.displayName, 'Queens College'); - // Update the tenant host to have uppercase characters - TenantsTestUtil.updateTenantAndWait( - globalAdminRestContext, - 'camtest', - { host: tenant2Host.toUpperCase() }, - err => { - assert.ok(!err); + // Check if the update was successful + // The host name should come back changed but lowercased + const tenant2UpperCaseAdminRestContext = TestsUtil.createTenantAdminRestContext( + tenant2Host.toUpperCase() + ); + RestAPI.Tenants.getTenant(tenant2UpperCaseAdminRestContext, null, (err, tenant) => { + assert.ok(!err); + assert.ok(tenant); + assert.strictEqual(tenant.alias, 'camtest'); + assert.strictEqual(tenant.host, tenant2Host.toLowerCase()); + assert.strictEqual(tenant.displayName, 'Queens College'); + + // Update the tenant host as the tenant admin + TenantsTestUtil.updateTenantAndWait( + tenant2UpperCaseAdminRestContext, + null, + { host: tenant3Host }, + err => { + assert.ok(!err); - // Check if the update was successful - // The host name should come back changed but lowercased - const tenant2UpperCaseAdminRestContext = TestsUtil.createTenantAdminRestContext( - tenant2Host.toUpperCase() - ); - RestAPI.Tenants.getTenant( - tenant2UpperCaseAdminRestContext, - null, - (err, tenant) => { - assert.ok(!err); - assert.ok(tenant); - assert.strictEqual(tenant.alias, 'camtest'); - assert.strictEqual(tenant.host, tenant2Host.toLowerCase()); - assert.strictEqual(tenant.displayName, 'Queens College'); - - // Update the tenant host as the tenant admin - TenantsTestUtil.updateTenantAndWait( - tenant2UpperCaseAdminRestContext, - null, - { host: tenant3Host }, - err => { - assert.ok(!err); - - // Check if the update was successful. - // The old host name should no longer be accepting requests - RestAPI.Tenants.getTenant( - tenant2UpperCaseAdminRestContext, - null, - (err, tenant) => { + // Check if the update was successful. + // The old host name should no longer be accepting requests + RestAPI.Tenants.getTenant(tenant2UpperCaseAdminRestContext, null, (err, tenant) => { + assert.ok(err); + assert.strictEqual(err.code, 418); + // The new host name should now be responding to requests + const tenant3AdminRestContext = TestsUtil.createTenantAdminRestContext(tenant3Host); + RestAPI.Tenants.getTenant(tenant3AdminRestContext, null, (err, tenant) => { + assert.ok(!err); + assert.ok(tenant); + assert.strictEqual(tenant.alias, 'camtest'); + assert.strictEqual(tenant.host, tenant3Host.toLowerCase()); + assert.strictEqual(tenant.displayName, 'Queens College'); + + // Update the tenant display name and host as the tenant admin + TenantsTestUtil.updateTenantAndWait( + tenant3AdminRestContext, + null, + { + displayName: tenant4Description, + host: tenant4Host + }, + err => { + assert.ok(!err); + + // Check if the update was successful. + // The old host name should no longer be accepting requests + RestAPI.Tenants.getTenant(tenant3AdminRestContext, null, (err, tenant) => { assert.ok(err); assert.strictEqual(err.code, 418); // The new host name should now be responding to requests - const tenant3AdminRestContext = TestsUtil.createTenantAdminRestContext( - tenant3Host - ); - RestAPI.Tenants.getTenant( - tenant3AdminRestContext, - null, - (err, tenant) => { - assert.ok(!err); - assert.ok(tenant); - assert.strictEqual(tenant.alias, 'camtest'); - assert.strictEqual( - tenant.host, - tenant3Host.toLowerCase() - ); - assert.strictEqual( - tenant.displayName, - 'Queens College' - ); - - // Update the tenant display name and host as the tenant admin - TenantsTestUtil.updateTenantAndWait( - tenant3AdminRestContext, - null, - { - displayName: tenant4Description, - host: tenant4Host - }, - err => { - assert.ok(!err); - - // Check if the update was successful. - // The old host name should no longer be accepting requests - RestAPI.Tenants.getTenant( - tenant3AdminRestContext, - null, - (err, tenant) => { - assert.ok(err); - assert.strictEqual(err.code, 418); - // The new host name should now be responding to requests - const tenant4AdminRestContext = TestsUtil.createTenantAdminRestContext( - tenant4Host - ); - RestAPI.Tenants.getTenant( - tenant4AdminRestContext, - null, - (err, tenant) => { - assert.ok(!err); - assert.ok(tenant); - assert.strictEqual( - tenant.alias, - 'camtest' - ); - assert.strictEqual( - tenant.host, - tenant4Host.toLowerCase() - ); - assert.strictEqual( - tenant.displayName, - tenant4Description - ); - - // Update the tenant display name and host as the tenant admin - TenantsTestUtil.updateTenantAndWait( - tenant4AdminRestContext, - null, - { - displayName: - 'Cambridge University Test', - host: 'cambridge.oae.com' - }, - err => { - assert.ok(!err); - - // Check if the update was successful - // The old host name should no longer be accepting requests - RestAPI.Tenants.getTenant( - tenant4AdminRestContext, - null, - (err, tenant) => { - assert.ok(err); - assert.strictEqual(err.code, 418); - - // The new host name should now be responding to requests - RestAPI.Tenants.getTenant( - camAdminRestContext, - null, - (err, tenant) => { - assert.ok(!err); - assert.ok(tenant); - assert.strictEqual( - tenant.alias, - 'camtest' - ); - assert.strictEqual( - tenant.host, - 'cambridge.oae.com' - ); - assert.strictEqual( - tenant.displayName, - 'Cambridge University Test' - ); - - // Update the country code, ensuring it changed - TenantsTestUtil.updateTenantAndWait( - camAdminRestContext, - null, - { countryCode: 'ca' }, - err => { - RestAPI.Tenants.getTenant( - camAdminRestContext, - null, - (err, tenant) => { - assert.ok(!err); - assert.strictEqual( - tenant.countryCode, - 'CA' - ); - - // Unset the country code, ensuring it changed - TenantsTestUtil.updateTenantAndWait( - camAdminRestContext, - null, - { countryCode: '' }, - err => { - RestAPI.Tenants.getTenant( - camAdminRestContext, - null, - ( - err, - tenant - ) => { - assert.ok( - !err - ); - assert.ok( - !tenant.countryCode - ); - return callback(); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } + const tenant4AdminRestContext = TestsUtil.createTenantAdminRestContext( + tenant4Host ); - } - ); - } - ); - } - ); - } - ); - }); - }); - } - ); + RestAPI.Tenants.getTenant(tenant4AdminRestContext, null, (err, tenant) => { + assert.ok(!err); + assert.ok(tenant); + assert.strictEqual(tenant.alias, 'camtest'); + assert.strictEqual(tenant.host, tenant4Host.toLowerCase()); + assert.strictEqual(tenant.displayName, tenant4Description); + + // Update the tenant display name and host as the tenant admin + TenantsTestUtil.updateTenantAndWait( + tenant4AdminRestContext, + null, + { + displayName: 'Cambridge University Test', + host: 'cambridge.oae.com' + }, + err => { + assert.ok(!err); + + // Check if the update was successful + // The old host name should no longer be accepting requests + RestAPI.Tenants.getTenant( + tenant4AdminRestContext, + null, + (err, tenant) => { + assert.ok(err); + assert.strictEqual(err.code, 418); + + // The new host name should now be responding to requests + RestAPI.Tenants.getTenant( + camAdminRestContext, + null, + (err, tenant) => { + assert.ok(!err); + assert.ok(tenant); + assert.strictEqual(tenant.alias, 'camtest'); + assert.strictEqual(tenant.host, 'cambridge.oae.com'); + assert.strictEqual( + tenant.displayName, + 'Cambridge University Test' + ); + + // Update the country code, ensuring it changed + TenantsTestUtil.updateTenantAndWait( + camAdminRestContext, + null, + { countryCode: 'ca' }, + err => { + RestAPI.Tenants.getTenant( + camAdminRestContext, + null, + (err, tenant) => { + assert.ok(!err); + assert.strictEqual(tenant.countryCode, 'CA'); + + // Unset the country code, ensuring it changed + TenantsTestUtil.updateTenantAndWait( + camAdminRestContext, + null, + { countryCode: '' }, + err => { + RestAPI.Tenants.getTenant( + camAdminRestContext, + null, + (err, tenant) => { + assert.ok(!err); + assert.ok(!tenant.countryCode); + return callback(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + }); + }); + } + ); + }); + }); + } + ); + }); + } + ); + }); + }); }); - } - ); + }); + }); }); } ); @@ -2637,27 +2308,17 @@ describe('Tenants', () => { // Updating the hostname to the SP hostname should fail const spHost = ShibbolethAPI.getSPHost(); - RestAPI.Tenants.updateTenant( - globalAdminRestContext, - tenantAlias, - { host: spHost }, - err => { + RestAPI.Tenants.updateTenant(globalAdminRestContext, tenantAlias, { host: spHost }, err => { + assert.ok(err); + assert.strictEqual(err.code, 400); + + // Updating the hostname to any case of the SP hostname should fail + RestAPI.Tenants.updateTenant(globalAdminRestContext, tenantAlias, { host: spHost.toUpperCase() }, err => { assert.ok(err); assert.strictEqual(err.code, 400); - - // Updating the hostname to any case of the SP hostname should fail - RestAPI.Tenants.updateTenant( - globalAdminRestContext, - tenantAlias, - { host: spHost.toUpperCase() }, - err => { - assert.ok(err); - assert.strictEqual(err.code, 400); - return callback(); - } - ); - } - ); + return callback(); + }); + }); } ); }); diff --git a/packages/oae-tests/lib/util.js b/packages/oae-tests/lib/util.js index a39d4c17b1..7c5c48cdec 100644 --- a/packages/oae-tests/lib/util.js +++ b/packages/oae-tests/lib/util.js @@ -13,45 +13,50 @@ * permissions and limitations under the License. */ -const path = require('path'); -const assert = require('assert'); -const stream = require('stream'); -const util = require('util'); -const async = require('async'); -const _ = require('underscore'); -const bodyParser = require('body-parser'); -const clone = require('clone'); -const express = require('express'); -const ShortId = require('shortid'); - -const AuthenticationAPI = require('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const Cassandra = require('oae-util/lib/cassandra'); -const ConfigTestUtil = require('oae-config/lib/test/util'); -const { Context } = require('oae-context'); -const LibraryAPI = require('oae-library'); -const { LoginId } = require('oae-authentication/lib/model'); -const multipart = require('oae-util/lib/middleware/multipart'); -const MQ = require('oae-util/lib/mq'); -const MQTestUtil = require('oae-util/lib/test/mq-util'); -const OAE = require('oae-util/lib/oae'); -const OaeUtil = require('oae-util/lib/util'); -const PreviewAPI = require('oae-preview-processor/lib/api'); -const PreviewConstants = require('oae-preview-processor/lib/constants'); -const PrincipalsAPI = require('oae-principals'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const Redis = require('oae-util/lib/redis'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const RestUtil = require('oae-rest/lib/util'); -const SearchTestUtil = require('oae-search/lib/test/util'); -const { Tenant } = require('oae-tenants/lib/model'); -const TenantsTestUtil = require('oae-tenants/lib/test/util'); -const { User } = require('oae-principals/lib/model'); +import path from 'path'; +import assert from 'assert'; +import stream from 'stream'; +import util from 'util'; + +import async from 'async'; +import _ from 'underscore'; + +import bodyParser from 'body-parser'; +import clone from 'clone'; +import express from 'express'; +import ShortId from 'shortid'; + +import * as AuthenticationAPI from 'oae-authentication'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import { Context } from 'oae-context'; +import * as LibraryAPI from 'oae-library'; + +import { LoginId } from 'oae-authentication/lib/model'; +import multipart from 'oae-util/lib/middleware/multipart'; +import * as MQ from 'oae-util/lib/mq'; +import * as MQTestUtil from 'oae-util/lib/test/mq-util'; +import * as OAE from 'oae-util/lib/oae'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as PreviewAPI from 'oae-preview-processor/lib/api'; +import PreviewConstants from 'oae-preview-processor/lib/constants'; +import * as PrincipalsAPI from 'oae-principals'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as Redis from 'oae-util/lib/redis'; +import * as RestAPI from 'oae-rest'; +import { RestContext } from 'oae-rest/lib/model'; +import * as RestUtil from 'oae-rest/lib/util'; +import * as SearchTestUtil from 'oae-search/lib/test/util'; +import { Tenant } from 'oae-tenants/lib/model'; +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; +import { User } from 'oae-principals/lib/model'; + +import { logger } from 'oae-logger'; const migrationRunner = require(path.join(process.cwd(), 'etc/migration/migration-runner.js')); -const log = require('oae-logger').logger('before-tests'); +const log = logger('before-tests'); /** * The name of the session cookie @@ -1346,7 +1351,7 @@ const isIntegrationTest = function() { return process.env.OAE_TEST_INTEGRATION !== 'false'; }; -module.exports = { +export { CONFIG_COOKIE_NAME, createTestServer, clearAllData, diff --git a/packages/oae-tests/runner/before-tests.js b/packages/oae-tests/runner/before-tests.js index 42393689fe..ceef1b921a 100644 --- a/packages/oae-tests/runner/before-tests.js +++ b/packages/oae-tests/runner/before-tests.js @@ -13,11 +13,13 @@ * permissions and limitations under the License. */ -const TestsUtil = require('oae-tests/lib/util'); -const { logger } = require('oae-logger'); +import * as TestsUtil from 'oae-tests/lib/util'; +import { logger } from 'oae-logger'; const log = logger('before-tests'); +const DEFAULT_TIMEOUT = 60000; + // eslint-disable-next-line no-unused-vars const { argv } = require('optimist') .usage('Run the Hilary tests.\nUsage: $0') @@ -39,7 +41,7 @@ before(function(callback) { // Create the configuration for the test const config = TestsUtil.createInitialTestConfig(); - this.timeout(config.test.timeout || 60000); + this.timeout(config.test.timeout || DEFAULT_TIMEOUT); TestsUtil.setUpBeforeTests(config, dropKeyspaceBeforeTest, callback); }); diff --git a/packages/oae-tincanapi/config/tincanapi.js b/packages/oae-tincanapi/config/tincanapi.js index e3e2ee31fa..7f8cd3c35b 100644 --- a/packages/oae-tincanapi/config/tincanapi.js +++ b/packages/oae-tincanapi/config/tincanapi.js @@ -13,18 +13,25 @@ * permissions and limitations under the License. */ -var Fields = require('oae-config/lib/fields'); +import * as Fields from 'oae-config/lib/fields'; -module.exports = { - 'title': 'OAE TinCan Module', - 'lrs': { - 'name': 'Learning Record Store configuration', - 'description': 'Learning Record Store configuration', - 'elements': { - 'enabled': new Fields.Bool('LRS enabled', 'Learning Record Store integration enabled for tenant', false, {'suppress': true}), - 'username': new Fields.Text('LRS username', 'The LRS username', '3HQ4Q12B57', {'suppress': true}), - 'password': new Fields.Text('LRS password', 'The LRS password', 'Wzoy9WJEqTYpf2E3pAjJTYAzZSmvpT3WO3iF4g3d', {'suppress': true}), - 'endpoint': new Fields.Text('LRS URL', 'The TinCan API REST endpoint', 'https://cloud.scorm.com/tc/3HQ4Q12B57/statements', {'suppress': true}) - } - } +export const title = 'OAE TinCan Module'; +export const lrs = { + name: 'Learning Record Store configuration', + description: 'Learning Record Store configuration', + elements: { + enabled: new Fields.Bool('LRS enabled', 'Learning Record Store integration enabled for tenant', false, { + suppress: true + }), + username: new Fields.Text('LRS username', 'The LRS username', '3HQ4Q12B57', { suppress: true }), + password: new Fields.Text('LRS password', 'The LRS password', 'Wzoy9WJEqTYpf2E3pAjJTYAzZSmvpT3WO3iF4g3d', { + suppress: true + }), + endpoint: new Fields.Text( + 'LRS URL', + 'The TinCan API REST endpoint', + 'https://cloud.scorm.com/tc/3HQ4Q12B57/statements', + { suppress: true } + ) + } }; diff --git a/packages/oae-tincanapi/lib/api.js b/packages/oae-tincanapi/lib/api.js index 55a0fa3e09..c7ec3340dd 100644 --- a/packages/oae-tincanapi/lib/api.js +++ b/packages/oae-tincanapi/lib/api.js @@ -13,20 +13,23 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const request = require('request'); +import ActivityEmitter from 'oae-activity/lib/internal/emitter'; -const ActivityAPI = require('oae-activity'); -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ConfigAPI = require('oae-config'); -const log = require('oae-logger').logger('oae-doc'); -const TenantsAPI = require('oae-tenants'); -const TenantsUtil = require('oae-tenants/lib/util'); +import _ from 'underscore'; +import request from 'request'; +import { logger } from 'oae-logger'; +import * as TenantsAPI from 'oae-tenants'; +import * as TenantsUtil from 'oae-tenants/lib/util'; -const { TinCanAPIConstants } = require('./constants'); -const TinCanModel = require('./model'); +import { ActivityConstants } from 'oae-activity/lib/constants'; +import { setUpConfig } from 'oae-config'; +import { TinCanAPIConstants } from './constants'; -const TinCanConfig = ConfigAPI.config('oae-tincanapi'); +import * as TinCanModel from './model'; + +const log = logger('oae-doc'); + +const TinCanConfig = setUpConfig('oae-tincanapi'); let config = null; @@ -42,7 +45,7 @@ const initializeTinCanAPI = function(_config, callback) { config = _config; // Listen for OAE activities that are taking place - ActivityAPI.emitter.on(ActivityConstants.events.ROUTED_ACTIVITIES, _processActivities); + ActivityEmitter.on(ActivityConstants.events.ROUTED_ACTIVITIES, _processActivities); callback(); }; @@ -65,10 +68,7 @@ const _processActivities = function(routedActivities) { // A triggered activity can end up as multiple routed activities (one for the actor, one for the target, one for each follower, ...) // We only need to send a single statement per triggered activity and only for the actor. // We can do this by only sending a statement if the activityStreamId we're dealing with is the same as the activity's actor - if ( - activity.actor[ActivityConstants.properties.OAE_ID] === resourceId && - streamType === 'activity' - ) { + if (activity.actor[ActivityConstants.properties.OAE_ID] === resourceId && streamType === 'activity') { // Verify that a valid activity object has been provided if (!activity.object[activity.object.objectType]) { return; @@ -82,9 +82,7 @@ const _processActivities = function(routedActivities) { tenantStatements[tenantAlias] = tenantStatements[tenantAlias] || []; // Construct the actor's profile link - const homePage = - TenantsUtil.getBaseUrl(TenantsAPI.getTenant(tenantAlias)) + - activity.actor.user.profilePath; + const homePage = TenantsUtil.getBaseUrl(TenantsAPI.getTenant(tenantAlias)) + activity.actor.user.profilePath; // Fill the actor, verb and object objects const actor = new TinCanModel.TinCanActor(activity.actor.user.displayName, homePage); @@ -174,7 +172,4 @@ const sendStatementsToLRS = function(statements, tenantAlias) { } }; -module.exports = { - initializeTinCanAPI, - sendActivitiesToLRS: sendStatementsToLRS -}; +export { initializeTinCanAPI, sendStatementsToLRS as sendActivitiesToLRS }; diff --git a/packages/oae-tincanapi/lib/constants.js b/packages/oae-tincanapi/lib/constants.js index 9d34cc616a..6dfaa5c87e 100644 --- a/packages/oae-tincanapi/lib/constants.js +++ b/packages/oae-tincanapi/lib/constants.js @@ -46,4 +46,4 @@ TinCanAPIConstants.verbs = { } }; -module.exports = { TinCanAPIConstants }; +export { TinCanAPIConstants }; diff --git a/packages/oae-tincanapi/lib/init.js b/packages/oae-tincanapi/lib/init.js index 16205c1e7f..84cf71d866 100644 --- a/packages/oae-tincanapi/lib/init.js +++ b/packages/oae-tincanapi/lib/init.js @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -const TinCanAPI = require('./api.js'); +import { initializeTinCanAPI } from './api.js'; -module.exports = function(config, callback) { - TinCanAPI.initializeTinCanAPI(config.tincanapi, callback); -}; +export function init(config, callback) { + initializeTinCanAPI(config.tincanapi, callback); +} diff --git a/packages/oae-tincanapi/lib/model.js b/packages/oae-tincanapi/lib/model.js index 5425703323..ad4ff63f9f 100644 --- a/packages/oae-tincanapi/lib/model.js +++ b/packages/oae-tincanapi/lib/model.js @@ -22,12 +22,12 @@ * @param {Object} object TinCan API Object information * @return {Statement} TinCan API Statement containing the body for the request to TinCan */ -module.exports.TinCanStatement = function(actor, verb, object) { - var that = {}; - that.actor = actor; - that.verb = verb; - that.object = object; - return that; +export const TinCanStatement = function(actor, verb, object) { + const that = {}; + that.actor = actor; + that.verb = verb; + that.object = object; + return that; }; /** @@ -38,15 +38,15 @@ module.exports.TinCanStatement = function(actor, verb, object) { * @param {String} homePage The link to the user's profile * @return {Actor} TinCan API Actor model containing information about the actor */ -module.exports.TinCanActor = function(displayName, homePage) { - var that = {}; - that.name = displayName; - that.objectType = 'Agent'; - that.account = { - 'homePage': homePage, - 'name': displayName - }; - return that; +export const TinCanActor = function(displayName, homePage) { + const that = {}; + that.name = displayName; + that.objectType = 'Agent'; + that.account = { + homePage, + name: displayName + }; + return that; }; /** @@ -58,19 +58,19 @@ module.exports.TinCanActor = function(displayName, homePage) { * @param {String} description The description of the activity object * @return {Object} TinCan API Object model containing information about the object */ -module.exports.TinCanObject = function(id, displayName, description) { - var that = {}; - that.id = id; - that.objectType = 'Activity'; - that.definition = { - 'name': { - 'en-US': displayName - }, - 'description': { - 'en-US': description - } - }; - return that; +export const TinCanObject = function(id, displayName, description) { + const that = {}; + that.id = id; + that.objectType = 'Activity'; + that.definition = { + name: { + 'en-US': displayName + }, + description: { + 'en-US': description + } + }; + return that; }; /** @@ -82,11 +82,11 @@ module.exports.TinCanObject = function(id, displayName, description) { * @param {String} display The display name of the verb * @return {Verb} TinCan API Verb model containing information about the verb */ -module.exports.TinCanVerb = function(id, display) { - var that = {}; - that.id = id; - that.display = { - 'en-US': display - }; - return that; +export const TinCanVerb = function(id, display) { + const that = {}; + that.id = id; + that.display = { + 'en-US': display + }; + return that; }; diff --git a/packages/oae-tincanapi/tests/test-tincanapi.js b/packages/oae-tincanapi/tests/test-tincanapi.js index a6e5e561ef..4a793c57b3 100644 --- a/packages/oae-tincanapi/tests/test-tincanapi.js +++ b/packages/oae-tincanapi/tests/test-tincanapi.js @@ -13,18 +13,14 @@ * visibilitys and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); -const express = require('express'); -const request = require('request'); +import assert from 'assert'; +import _ from 'underscore'; -const ConfigTestUtil = require('oae-config/lib/test/util'); -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const TestsUtil = require('oae-tests'); +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; -const ActivityTestsUtil = require('oae-activity/lib/test/util'); -const TinCanConfig = require('oae-config').config('oae-tincanapi'); +import * as ActivityTestsUtil from 'oae-activity/lib/test/util'; describe('TinCanAPI', () => { // Will be set as a function that is executed when sending requests to the API @@ -75,15 +71,10 @@ describe('TinCanAPI', () => { * Disables the LRS for the tenant after each test */ afterEach(callback => { - ConfigTestUtil.updateConfigAndWait( - camAdminRestContext, - null, - { 'oae-tincanapi/lrs/enabled': false }, - err => { - assert.ok(!err); - return callback(); - } - ); + ConfigTestUtil.updateConfigAndWait(camAdminRestContext, null, { 'oae-tincanapi/lrs/enabled': false }, err => { + assert.ok(!err); + return callback(); + }); }); /** @@ -103,60 +94,62 @@ describe('TinCanAPI', () => { const testUser = _.values(users)[0]; // Collect any existing activities so they will not interfere with this test - ActivityTestsUtil.collectAndGetActivityStream( - testUser.restContext, - null, - null, - (err, activityStream) => { + ActivityTestsUtil.collectAndGetActivityStream(testUser.restContext, null, null, (err, activityStream) => { + assert.ok(!err); + + // Enable sending activities to the LRS as the default value is false + ConfigTestUtil.updateConfigAndWait(camAdminRestContext, null, { 'oae-tincanapi/lrs/enabled': true }, err => { assert.ok(!err); - // Enable sending activities to the LRS as the default value is false - ConfigTestUtil.updateConfigAndWait( - camAdminRestContext, - null, - { 'oae-tincanapi/lrs/enabled': true }, - err => { - assert.ok(!err); + const testLinks = {}; + let activitiesCollected = false; + let tincanChecked = false; - const testLinks = {}; - let activitiesCollected = false; - let tincanChecked = false; - - // Define the function that will get executed when our dummy LRS receives a request - // This should only happen when we trigger an activity collection cycle at the end of the test - onRequest = function(req) { - // Check each activity if it matches the original - _.each(req.body, (val, key) => { - assert.strictEqual(val.actor.name, testUser.user.displayName); - setTimeout(() => { - assert.ok(testLinks[val.object.id]); - assert.strictEqual( - val.object.definition.name['en-US'], - testLinks[val.object.id].displayName - ); - assert.strictEqual( - val.object.definition.description['en-US'], - testLinks[val.object.id].description - ); - }, 2000); - }); + // Define the function that will get executed when our dummy LRS receives a request + // This should only happen when we trigger an activity collection cycle at the end of the test + onRequest = function(req) { + // Check each activity if it matches the original + _.each(req.body, (val, key) => { + assert.strictEqual(val.actor.name, testUser.user.displayName); + setTimeout(() => { + assert.ok(testLinks[val.object.id]); + assert.strictEqual(val.object.definition.name['en-US'], testLinks[val.object.id].displayName); + assert.strictEqual(val.object.definition.description['en-US'], testLinks[val.object.id].description); + }, 2000); + }); + + tincanChecked = true; + + // Because of the async nature of collecting activities and submitting them to the LRS + // it's possible that the `collectAndGetActivityStream` callback hasn't been called yet + if (tincanChecked && activitiesCollected) { + callback(); + } + }; - tincanChecked = true; + // Create a new link + RestAPI.Content.createLink( + testUser.restContext, + 'Link1', + 'The first link', + 'public', + 'http://www.google.be', + [], + [], + [], + (err, link) => { + assert.ok(!err); - // Because of the async nature of collecting activities and submitting them to the LRS - // it's possible that the `collectAndGetActivityStream` callback hasn't been called yet - if (tincanChecked && activitiesCollected) { - callback(); - } - }; + // Store the created link + testLinks[link.id] = link; // Create a new link RestAPI.Content.createLink( testUser.restContext, - 'Link1', - 'The first link', - 'public', - 'http://www.google.be', + 'Link2', + 'The second link', + 'private', + 'http://www.google.fr', [], [], [], @@ -169,10 +162,10 @@ describe('TinCanAPI', () => { // Create a new link RestAPI.Content.createLink( testUser.restContext, - 'Link2', - 'The second link', - 'private', - 'http://www.google.fr', + 'Link3', + 'The third link', + 'public', + 'http://www.google.nl', [], [], [], @@ -182,39 +175,21 @@ describe('TinCanAPI', () => { // Store the created link testLinks[link.id] = link; - // Create a new link - RestAPI.Content.createLink( + // Force an activity collection cycle that will send the activities to our dummy LRS + // The test will be ended there + ActivityTestsUtil.collectAndGetActivityStream( testUser.restContext, - 'Link3', - 'The third link', - 'public', - 'http://www.google.nl', - [], - [], - [], - (err, link) => { + null, + null, + (err, activityStream) => { assert.ok(!err); + activitiesCollected = true; - // Store the created link - testLinks[link.id] = link; - - // Force an activity collection cycle that will send the activities to our dummy LRS - // The test will be ended there - ActivityTestsUtil.collectAndGetActivityStream( - testUser.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); - activitiesCollected = true; - - // Because of the async nature of collecting activities and submitting them to the LRS - // it's possible that the activities submitted to the LRS haven't been checked for correctness yet - if (tincanChecked && activitiesCollected) { - callback(); - } - } - ); + // Because of the async nature of collecting activities and submitting them to the LRS + // it's possible that the activities submitted to the LRS haven't been checked for correctness yet + if (tincanChecked && activitiesCollected) { + callback(); + } } ); } @@ -223,8 +198,8 @@ describe('TinCanAPI', () => { ); } ); - } - ); + }); + }); }); }); @@ -238,48 +213,34 @@ describe('TinCanAPI', () => { const testUser = _.values(users)[0]; // Collect any existing activities so they will not interfere with this test - ActivityTestsUtil.collectAndGetActivityStream( - testUser.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); - - onRequest = function(req) { - assert.fail( - null, - null, - 'No statements should be sent when LRS integration is disabled' - ); - }; + ActivityTestsUtil.collectAndGetActivityStream(testUser.restContext, null, null, (err, activityStream) => { + assert.ok(!err); - // Create a new link - RestAPI.Content.createLink( - testUser.restContext, - 'Link1', - 'The first link', - 'public', - 'http://www.google.be', - [], - [], - [], - (err, link) => { + onRequest = function(req) { + assert.fail(null, null, 'No statements should be sent when LRS integration is disabled'); + }; + + // Create a new link + RestAPI.Content.createLink( + testUser.restContext, + 'Link1', + 'The first link', + 'public', + 'http://www.google.be', + [], + [], + [], + (err, link) => { + assert.ok(!err); + + // Force the activities + ActivityTestsUtil.collectAndGetActivityStream(testUser.restContext, null, null, (err, activityStream) => { assert.ok(!err); - - // Force the activities - ActivityTestsUtil.collectAndGetActivityStream( - testUser.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); - callback(); - } - ); - } - ); - } - ); + callback(); + }); + } + ); + }); }); }); @@ -293,102 +254,92 @@ describe('TinCanAPI', () => { const camUser = _.values(users)[0]; // Collect any existing activities so they will not interfere with this test - ActivityTestsUtil.collectAndGetActivityStream( - camUser.restContext, - null, - null, - (err, activityStream) => { + ActivityTestsUtil.collectAndGetActivityStream(camUser.restContext, null, null, (err, activityStream) => { + assert.ok(!err); + + // Enable sending activities to the LRS as the default value is false + // Note that we only enable it for the Cambridge tenant + ConfigTestUtil.updateConfigAndWait(camAdminRestContext, null, { 'oae-tincanapi/lrs/enabled': true }, err => { assert.ok(!err); - // Enable sending activities to the LRS as the default value is false - // Note that we only enable it for the Cambridge tenant - ConfigTestUtil.updateConfigAndWait( - camAdminRestContext, - null, - { 'oae-tincanapi/lrs/enabled': true }, - err => { - assert.ok(!err); + let activitiesCollected = false; + let tincanChecked = false; + const tincanSent = false; - let activitiesCollected = false; - let tincanChecked = false; - const tincanSent = false; - - // Define the function that will get executed when our dummy LRS receives a request - // This should only happen when we trigger an activity collection cycle at the end of the test - onRequest = function(req) { - // Check each activity if it matches the original - _.each(req.body, (val, key) => { - assert.strictEqual(val.actor.name, camUser.user.displayName); - assert.strictEqual(val.actor.account.name, camUser.user.publicAlias); - }); - - tincanChecked = true; - - // Because of the async nature of collecting activities and submitting them to the LRS - // it's possible that the `collectAndGetActivityStream` callback hasn't been called yet - if (tincanChecked && activitiesCollected) { - callback(); - } - }; + // Define the function that will get executed when our dummy LRS receives a request + // This should only happen when we trigger an activity collection cycle at the end of the test + onRequest = function(req) { + // Check each activity if it matches the original + _.each(req.body, (val, key) => { + assert.strictEqual(val.actor.name, camUser.user.displayName); + assert.strictEqual(val.actor.account.name, camUser.user.publicAlias); + }); + + tincanChecked = true; + + // Because of the async nature of collecting activities and submitting them to the LRS + // it's possible that the `collectAndGetActivityStream` callback hasn't been called yet + if (tincanChecked && activitiesCollected) { + callback(); + } + }; - // Create a new link on the Cambridge tenant - RestAPI.Content.createLink( - camUser.restContext, - 'Link1', - 'The first link', - 'public', - 'http://www.google.be', - [], - [], - [], - (err, link) => { - assert.ok(!err); + // Create a new link on the Cambridge tenant + RestAPI.Content.createLink( + camUser.restContext, + 'Link1', + 'The first link', + 'public', + 'http://www.google.be', + [], + [], + [], + (err, link) => { + assert.ok(!err); - // Create a new user on the GT tenant - TestsUtil.generateTestUsers(gtAdminRestContext, 1, (err, gtUsers) => { + // Create a new user on the GT tenant + TestsUtil.generateTestUsers(gtAdminRestContext, 1, (err, gtUsers) => { + assert.ok(!err); + + // Store the gtUser + const gtUser = _.values(users)[0]; + + // Create a new link on the GT tenant + RestAPI.Content.createLink( + gtUser.restContext, + 'Link2', + 'The second link', + 'private', + 'http://www.google.nl', + [], + [], + [], + (err, link) => { assert.ok(!err); - // Store the gtUser - const gtUser = _.values(users)[0]; - - // Create a new link on the GT tenant - RestAPI.Content.createLink( - gtUser.restContext, - 'Link2', - 'The second link', - 'private', - 'http://www.google.nl', - [], - [], - [], - (err, link) => { + // Force an activity collection cycle. It doesn't really matter which restContext we pass in, as that is only used to retrieve the activity stream + ActivityTestsUtil.collectAndGetActivityStream( + camUser.restContext, + null, + null, + (err, activityStream) => { assert.ok(!err); + activitiesCollected = true; - // Force an activity collection cycle. It doesn't really matter which restContext we pass in, as that is only used to retrieve the activity stream - ActivityTestsUtil.collectAndGetActivityStream( - camUser.restContext, - null, - null, - (err, activityStream) => { - assert.ok(!err); - activitiesCollected = true; - - // Because of the async nature of collecting activities and submitting them to the LRS - // it's possible that the activities submitted to the LRS haven't been checked for correctness yet - if (tincanChecked && activitiesCollected) { - callback(); - } - } - ); + // Because of the async nature of collecting activities and submitting them to the LRS + // it's possible that the activities submitted to the LRS haven't been checked for correctness yet + if (tincanChecked && activitiesCollected) { + callback(); + } } ); - }); - } - ); + } + ); + }); } ); - } - ); + }); + }); }); }); }); diff --git a/packages/oae-ui/config/skin.js b/packages/oae-ui/config/skin.js index 614428ea91..3bd562750d 100644 --- a/packages/oae-ui/config/skin.js +++ b/packages/oae-ui/config/skin.js @@ -13,15 +13,20 @@ * permissions and limitations under the License. */ -var Fields = require('oae-config/lib/fields'); +import { Text } from 'oae-config/lib/fields'; module.exports = { - 'title': 'OAE UI Module', - 'skin': { - 'name': 'Skin settings', - 'description': 'Define the skin settings', - 'elements': { - 'variables': new Fields.Text('JSON Variables', 'A JSON dictionary that holds the less variables. Each key is a less variable', {}, {'tenantOverride': true, 'suppress': true, 'globalAdminOnly': false}) - } + title: 'OAE UI Module', + skin: { + name: 'Skin settings', + description: 'Define the skin settings', + elements: { + variables: new Text( + 'JSON Variables', + 'A JSON dictionary that holds the less variables. Each key is a less variable', + {}, + { tenantOverride: true, suppress: true, globalAdminOnly: false } + ) } + } }; diff --git a/packages/oae-ui/lib/api.js b/packages/oae-ui/lib/api.js index 12bdb100d0..0060d42378 100644 --- a/packages/oae-ui/lib/api.js +++ b/packages/oae-ui/lib/api.js @@ -13,31 +13,35 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const path = require('path'); -const url = require('url'); -const util = require('util'); -const _ = require('underscore'); -const $ = require('cheerio'); -const Globalize = require('globalize'); -const less = require('less'); -const marked = require('marked'); -const PropertiesParser = require('properties-parser'); -const readdirp = require('readdirp'); -const watch = require('watch'); - -const ConfigAPI = require('oae-config'); -const ContentUtil = require('oae-content/lib/internal/util'); -const EmitterAPI = require('oae-emitter'); -const Sanitization = require('oae-util/lib/sanitization'); -const TZ = require('oae-util/lib/tz'); -const { Validator } = require('oae-util/lib/validator'); -const log = require('oae-logger').logger('oae-ui'); - -const { UIConstants } = require('./constants'); +import fs from 'fs'; +import path from 'path'; +import url from 'url'; +import util from 'util'; +import _ from 'underscore'; +import $ from 'cheerio'; + +import Globalize from 'globalize'; +import less from 'less'; +import marked from 'marked'; +import PropertiesParser from 'properties-parser'; +import readdirp from 'readdirp'; +import watch from 'watch'; + +import * as ConfigAPI from 'oae-config'; + +import * as ContentUtil from 'oae-content/lib/internal/util'; +import * as EmitterAPI from 'oae-emitter'; +import * as Sanitization from 'oae-util/lib/sanitization'; +import * as TZ from 'oae-util/lib/tz'; +import { Validator } from 'oae-util/lib/validator'; +import { logger } from 'oae-logger'; + +import { UIConstants } from './constants'; + +const log = logger('oae-ui'); // The Config object for the UI module. -const uiConfig = ConfigAPI.config('oae-ui'); +const uiConfig = ConfigAPI.setUpConfig('oae-ui'); // The cached skin variables let cachedSkinVariables = null; @@ -127,10 +131,7 @@ ConfigAPI.eventEmitter.on('update', tenantAlias => { // Don't delete it from the cache just yet as we might still be serving requests. _generateSkin(tenantAlias, err => { if (err) { - log().error( - { err, tenantAlias }, - 'Could not re-cache the tenant skin after a config update.' - ); + log().error({ err, tenantAlias }, 'Could not re-cache the tenant skin after a config update.'); } emitter.emit('skinParsed'); @@ -217,11 +218,9 @@ const cacheWidgetManifests = function() { widgetManifestCache[widgetId] = JSON.parse(widgetManifest); } catch (error) { widgetManifestCache[widgetId] = {}; - log().error( - { err: error, widgetId, path: entry.fullPath }, - 'Could not parse the widget manifest file' - ); + log().error({ err: error, widgetId, path: entry.fullPath }, 'Could not parse the widget manifest file'); } + widgetManifestCache[widgetId].id = widgetId; widgetManifestCache[widgetId].path = entry.parentDir + '/'; }) @@ -265,15 +264,12 @@ const getStaticBatch = function(files, callback) { files = _.uniq(files); // Make sure that all provided filenames are real strings for (let i = 0; i < files.length; i++) { - validator - .check(files[i], { code: 400, msg: 'A valid file path needs to be provided' }) - .notEmpty(); + validator.check(files[i], { code: 400, msg: 'A valid file path needs to be provided' }).notEmpty(); // Make sure that only absolute paths are allowed. All paths that contain a '../' have the potential of // exposing private server files - validator - .check(files[i], { code: 400, msg: 'Only absolute paths are allowed' }) - .notContains('../'); + validator.check(files[i], { code: 400, msg: 'Only absolute paths are allowed' }).notContains('../'); } + validator.check(files.length, { code: 400, msg: 'At least one file must be provided' }).min(1); if (validator.hasErrors()) { return callback(validator.getFirstError()); @@ -389,6 +385,7 @@ const getSkin = function(ctx, callback) { if (cachedSkins[tenantAlias]) { return callback(null, cachedSkins[tenantAlias]); } + _generateSkin(tenantAlias, callback); }; @@ -431,6 +428,7 @@ const getSkinVariables = function(ctx, tenantAlias, callback) { if (err) { return callback(err); } + // Get the values for this tenant. const tenantVariables = _getTenantSkinVariableValues(tenantAlias); @@ -488,9 +486,7 @@ const getSkinVariables = function(ctx, tenantAlias, callback) { }; // Make sure that the subsection exists const section = sections[variable.section.name]; - section.subsections[variable.subsection.name] = section.subsections[ - variable.subsection.name - ] || { + section.subsections[variable.subsection.name] = section.subsections[variable.subsection.name] || { name: variable.subsection.name, index: variable.subsection.index, variables: [] @@ -664,11 +660,7 @@ const _cacheSkinVariables = function(callback) { let subsectionMatch = null; // Section declaration - if ( - rule.value && - typeof rule.value === 'string' && - (sectionMatch = rule.value.match(sectionRegex)) - ) { + if (rule.value && typeof rule.value === 'string' && (sectionMatch = rule.value.match(sectionRegex))) { // Get the name of this section. section = sectionMatch[1]; sections.push(section); @@ -692,12 +684,7 @@ const _cacheSkinVariables = function(callback) { // This should be defined in the previous rule. const docRule = tree.rules[i - 1]; let description = 'TODO'; - if ( - docRule && - docRule.value && - typeof docRule.value === 'string' && - docRule.value.substring(0, 2) === '/*' - ) { + if (docRule && docRule.value && typeof docRule.value === 'string' && docRule.value.substring(0, 2) === '/*') { description = docRule.value.replace('/* ', '').replace('*/', ''); } @@ -711,6 +698,7 @@ const _cacheSkinVariables = function(callback) { // If (/-color$/.test(name)) { type = UIConstants.variables.types.COLOR; } + if (name.endsWith('-url')) { // If (/-url$/.test(name)) { type = UIConstants.variables.types.URL; @@ -789,6 +777,7 @@ const _getTenantSkinVariableValues = function(tenantAlias) { if (!variables || !_.isObject(variables)) { return {}; } + return variables; }; @@ -844,11 +833,7 @@ const _replaceOptimizedPaths = function(skinVariables) { const variableMetadata = cachedSkinVariables[key]; // Try to apply optimized paths if the variable is a valid URL - if ( - variableMetadata && - variableMetadata.type === UIConstants.variables.types.URL && - urlRegex.test(value) - ) { + if (variableMetadata && variableMetadata.type === UIConstants.variables.types.URL && urlRegex.test(value)) { // Strip out the enclosing quotes const url = value.replace(urlRegex, '$1').trim(); if (hashes[url]) { @@ -877,6 +862,7 @@ const uploadLogoFile = function(ctx, file, tenantAlias, callback) { if (!ctx.user() || !ctx.user().isAdmin(tenantAlias)) { return callback({ code: 401, msg: 'Only administrators can upload new logos for tenants' }); } + const extension = file.name.split('.').pop(); if (!extension.match(/(gif|jpe?g|png)$/i)) { return callback({ code: 500, msg: 'File has an invalid mime type' }); @@ -889,6 +875,7 @@ const uploadLogoFile = function(ctx, file, tenantAlias, callback) { if (err) { return callback(err); } + const signedUrl = ContentUtil.getSignedDownloadUrl(ctx, uri, -1, -1); return callback(null, signedUrl); }); @@ -1079,6 +1066,7 @@ const renderTemplate = function(template, data, locale) { if (text && href && href !== '#') { return util.format('%s (%s)', text, href); } + return $(this); }); } @@ -1114,6 +1102,7 @@ const renderTemplate = function(template, data, locale) { if (text.length > maxChars) { text = text.substring(0, maxChars) + '...'; } + return text; }, @@ -1256,11 +1245,13 @@ const renderTemplate = function(template, data, locale) { // If the link already has `http` in it (e.g., twitter profile pics) we return as-is } + if (link.indexOf('http') === 0) { return link; // Otherwise we prefix it with the base url } + return baseUrl + link; }, @@ -1327,6 +1318,7 @@ const compileTemplate = function(template) { if (fs.existsSync(path)) { return fs.readFileSync(path, 'utf8'); } + log().warn({ path }, 'Could not find an underscore template'); return ''; }); @@ -1402,7 +1394,7 @@ const _uiRequire = function(path) { return require(uiDirectory + path); }; -module.exports = { +export { emitter, init, getWidgetManifests, diff --git a/packages/oae-ui/lib/constants.js b/packages/oae-ui/lib/constants.js index 98c6638ef8..d63d7f9f8c 100644 --- a/packages/oae-ui/lib/constants.js +++ b/packages/oae-ui/lib/constants.js @@ -27,4 +27,4 @@ UIConstants.paths = { BASE_SKIN: '/shared/oae/css/oae.skin.less' }; -module.exports = { UIConstants }; +export { UIConstants }; diff --git a/packages/oae-ui/lib/init.js b/packages/oae-ui/lib/init.js index 5ed07f35e8..1435424328 100644 --- a/packages/oae-ui/lib/init.js +++ b/packages/oae-ui/lib/init.js @@ -13,12 +13,14 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const log = require('oae-logger').logger('oae-ui-init'); -const UIAPI = require('./api'); +import { realpathSync } from 'fs'; +import { logger } from 'oae-logger'; +import * as UIAPI from './api'; -module.exports = function(config, callback) { - const uiDirectory = fs.realpathSync(config.ui.path); +const log = logger('oae-ui-init'); + +export const init = function(config, callback) { + const uiDirectory = realpathSync(config.ui.path); // The hashes.json file can be found in the root folder of the optimized build folder const hashesPath = uiDirectory + '/hashes.json'; diff --git a/packages/oae-ui/lib/rest.js b/packages/oae-ui/lib/rest.js index fd95f6be6e..d14d863427 100644 --- a/packages/oae-ui/lib/rest.js +++ b/packages/oae-ui/lib/rest.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -const OAE = require('oae-util/lib/oae'); -const OaeUtil = require('oae-util/lib/util'); +import * as OAE from 'oae-util/lib/oae'; +import * as OaeUtil from 'oae-util/lib/util'; -const UIAPI = require('./api'); +import * as UIAPI from './api'; /** * @REST getUiWidgets @@ -110,6 +110,7 @@ const _getLogo = function(req, res) { if (err) { return res.status(err.code).send(err.msg); } + return res.status(200).send(css); }); }; diff --git a/packages/oae-ui/lib/test/util.js b/packages/oae-ui/lib/test/util.js index 13668d0380..fefcb4f077 100644 --- a/packages/oae-ui/lib/test/util.js +++ b/packages/oae-ui/lib/test/util.js @@ -13,11 +13,9 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); - -const ConfigTestUtil = require('oae-config/lib/test/util'); - -const UIAPI = require('oae-ui'); +import _ from 'underscore'; +import * as ConfigTestUtil from 'oae-config/lib/test/util'; +import * as UIAPI from 'oae-ui'; /** * Updates the skin variables for a tenant and waits till the CSS has been regenerated. @@ -87,6 +85,4 @@ const updateSkinAndWait = function(restCtx, tenantAlias, skinConfig, callback) { UIAPI.emitter.once('skinParsed', _updateListener); }; -module.exports = { - updateSkinAndWait -}; +export { updateSkinAndWait }; diff --git a/packages/oae-ui/tests/test-ui.js b/packages/oae-ui/tests/test-ui.js index 534a498ad1..9c295908ba 100644 --- a/packages/oae-ui/tests/test-ui.js +++ b/packages/oae-ui/tests/test-ui.js @@ -13,20 +13,20 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const util = require('util'); -const path = require('path'); -const _ = require('underscore'); +import assert from 'assert'; +import fs from 'fs'; +import util from 'util'; +import path from 'path'; +import _ from 'underscore'; -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const TenantsTestUtil = require('oae-tenants/lib/test/util'); -const TestsUtil = require('oae-tests'); +import * as RestAPI from 'oae-rest'; +import { RestContext } from 'oae-rest/lib/model'; +import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; +import * as TestsUtil from 'oae-tests'; -const UIAPI = require('oae-ui'); -const { UIConstants } = require('oae-ui/lib/constants'); -const UITestUtil = require('oae-ui/lib/test/util'); +import * as UIAPI from 'oae-ui'; +import { UIConstants } from 'oae-ui/lib/constants'; +import * as UITestUtil from 'oae-ui/lib/test/util'; describe('UI', () => { // Rest context that can be used every time we need to make a request as an anonymous user to the cambridge tenant @@ -232,15 +232,10 @@ describe('UI', () => { const skinConfig = { 'body-background-color': '#' + DEFAULT_BODY_BACKGROUND_COLOR }; - UITestUtil.updateSkinAndWait( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - skinConfig, - err => { - assert.ok(!err); - callback(); - } - ); + UITestUtil.updateSkinAndWait(globalAdminRestContext, global.oaeTests.tenants.cam.alias, skinConfig, err => { + assert.ok(!err); + callback(); + }); }); /** @@ -325,23 +320,18 @@ describe('UI', () => { // Sanity-check correct parsing checkSkin(anonymousCamRestContext, expectedOldBackgroundColor, () => { // Update the cambridge skin. - UITestUtil.updateSkinAndWait( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - skinConfig, - err => { - assert.ok(!err); + UITestUtil.updateSkinAndWait(globalAdminRestContext, global.oaeTests.tenants.cam.alias, skinConfig, err => { + assert.ok(!err); - // Check the skin for the new value. - checkSkin(anonymousCamRestContext, expectedNewBackgroundColor, () => { - // Check the global admin skin is unchanged. - checkSkin(globalAdminRestContext, DEFAULT_BODY_BACKGROUND_COLOR, () => { - // Check the GT skin is unchanged. - checkSkin(anonymousGTRestContext, DEFAULT_BODY_BACKGROUND_COLOR, callback); - }); + // Check the skin for the new value. + checkSkin(anonymousCamRestContext, expectedNewBackgroundColor, () => { + // Check the global admin skin is unchanged. + checkSkin(globalAdminRestContext, DEFAULT_BODY_BACKGROUND_COLOR, () => { + // Check the GT skin is unchanged. + checkSkin(anonymousGTRestContext, DEFAULT_BODY_BACKGROUND_COLOR, callback); }); - } - ); + }); + }); }); }; @@ -375,21 +365,15 @@ describe('UI', () => { // Verify the body background color for the `Branding` section assert.ok(data.results[0].subsections[0].variables.length > 0); - assert.strictEqual( - data.results[0].subsections[0].variables[0].type, - UIConstants.variables.types.COLOR - ); - assert.strictEqual( - data.results[0].subsections[0].variables[0].value, - expectedBackgroundColor - ); + assert.strictEqual(data.results[0].subsections[0].variables[0].type, UIConstants.variables.types.COLOR); + assert.strictEqual(data.results[0].subsections[0].variables[0].value, expectedBackgroundColor); callback(); }); }; /* - * Updating the config should result in a change in the skin. - */ + * Updating the config should result in a change in the skin. + */ it('verify updating the skin', callback => { updateSkinAndCheck( anonymousCamRestContext, @@ -401,8 +385,8 @@ describe('UI', () => { }); /* - * Submitting incorrect CSS values should not break the CSS skin generation. - */ + * Submitting incorrect CSS values should not break the CSS skin generation. + */ it('verify that submitting incorrect CSS values does not break skinning', callback => { updateSkinAndCheck( anonymousCamRestContext, @@ -414,9 +398,9 @@ describe('UI', () => { }); /* - * When submitting skin values with keys that are not used, - * this should not break skin generation. - */ + * When submitting skin values with keys that are not used, + * this should not break skin generation. + */ it('verify that submitting unused key does not break skinning', callback => { updateSkinAndCheck( anonymousCamRestContext, @@ -428,9 +412,9 @@ describe('UI', () => { }); /* - * When you update the config with new skin values, - * these should be returned in the variables endpoint. - */ + * When you update the config with new skin values, + * these should be returned in the variables endpoint. + */ it('verify that variables get updated with values from the config', callback => { // Sanity check the default value. checkVariables(global.oaeTests.tenants.cam.alias, '#eceae5', () => { @@ -449,38 +433,30 @@ describe('UI', () => { }); /* - * Test that verifies that only admininstrators are able to retrieve skin variables - */ + * Test that verifies that only admininstrators are able to retrieve skin variables + */ it('verify only administrators can retrieve skin variabes', callback => { - RestAPI.UI.getSkinVariables( - anonymousGlobalRestContext, - global.oaeTests.tenants.cam.alias, - (err, data) => { + RestAPI.UI.getSkinVariables(anonymousGlobalRestContext, global.oaeTests.tenants.cam.alias, (err, data) => { + assert.strictEqual(err.code, 401); + RestAPI.UI.getSkinVariables(anonymousCamRestContext, null, (err, data) => { assert.strictEqual(err.code, 401); - RestAPI.UI.getSkinVariables(anonymousCamRestContext, null, (err, data) => { - assert.strictEqual(err.code, 401); - RestAPI.UI.getSkinVariables( - globalAdminRestContext, - global.oaeTests.tenants.cam.alias, - (err, data) => { + RestAPI.UI.getSkinVariables(globalAdminRestContext, global.oaeTests.tenants.cam.alias, (err, data) => { + assert.ok(!err); + RestAPI.UI.getSkinVariables(camAdminRestContext, null, (err, data) => { + assert.ok(!err); + TestsUtil.generateTestUsers(camAdminRestContext, 1, (err, users) => { assert.ok(!err); - RestAPI.UI.getSkinVariables(camAdminRestContext, null, (err, data) => { - assert.ok(!err); - TestsUtil.generateTestUsers(camAdminRestContext, 1, (err, users) => { - assert.ok(!err); - const user = _.values(users)[0]; - RestAPI.UI.getSkinVariables(user.restContext, null, (err, data) => { - assert.strictEqual(err.code, 401); - return callback(); - }); - }); + const user = _.values(users)[0]; + RestAPI.UI.getSkinVariables(user.restContext, null, (err, data) => { + assert.strictEqual(err.code, 401); + return callback(); }); - } - ); + }); + }); }); - } - ); + }); + }); }); /** @@ -500,10 +476,7 @@ describe('UI', () => { assert.ok(!err); // Get the default logo url, parsing out the single quotes - const defaultLogoUrl = _getSkinVariableValue('institutional-logo-url', variables).slice( - 1, - -1 - ); + const defaultLogoUrl = _getSkinVariableValue('institutional-logo-url', variables).slice(1, -1); // Create some mock hash mappings to test with const hashes = { @@ -523,11 +496,7 @@ describe('UI', () => { assert.ok(!err); // Verify that the default logoUrl was replaced - assert.strictEqual( - css.indexOf(defaultLogoUrl), - -1, - 'Expected the default logo url to be replaced' - ); + assert.strictEqual(css.indexOf(defaultLogoUrl), -1, 'Expected the default logo url to be replaced'); assert.notStrictEqual( css.indexOf('/optimized/logo/path'), -1, @@ -542,77 +511,67 @@ describe('UI', () => { }; // Set the skin configuration so that only the institutional logo should be substituted by the hashed files - UITestUtil.updateSkinAndWait( - globalAdminRestContext, - testTenantAlias, - skinConfig, - err => { + UITestUtil.updateSkinAndWait(globalAdminRestContext, testTenantAlias, skinConfig, err => { + assert.ok(!err); + + RestAPI.UI.getSkin(testTenantAdminRestContext, (err, css, response) => { assert.ok(!err); - RestAPI.UI.getSkin(testTenantAdminRestContext, (err, css, response) => { + // Verify /test/directory was replaced + assert.strictEqual( + css.indexOf('/test/directory'), + -1, + 'Expected the generated skin to have "/test/directory" replaced by the mapping' + ); + assert.notStrictEqual( + css.indexOf('/test/target/directory'), + -1, + 'Expected the generated skin to have "/test/directory" replaced by the mapping' + ); + + // Verify google.ca was not replaced + assert.notStrictEqual( + css.indexOf('http://www.google.ca/haha.png'), + -1, + 'Expected the generated skin to not have "http://www.google.ca/haha.png" replaced by anything' + ); + + // Verify /test/color was not replaced + assert.notStrictEqual( + css.indexOf('/test/color'), + -1, + 'Did not expected the generated skin to replace "/test/color"' + ); + assert.strictEqual( + css.indexOf('/test/target/color'), + -1, + 'Did not expected the generated skin to replace "/test/color"' + ); + + // Mingle with the spacing to make sure we're somewhat robust for user input + skinConfig = { 'institutional-logo-url': " ' /test/directory ' " }; + UITestUtil.updateSkinAndWait(globalAdminRestContext, testTenantAlias, skinConfig, err => { assert.ok(!err); - // Verify /test/directory was replaced - assert.strictEqual( - css.indexOf('/test/directory'), - -1, - 'Expected the generated skin to have "/test/directory" replaced by the mapping' - ); - assert.notStrictEqual( - css.indexOf('/test/target/directory'), - -1, - 'Expected the generated skin to have "/test/directory" replaced by the mapping' - ); - - // Verify google.ca was not replaced - assert.notStrictEqual( - css.indexOf('http://www.google.ca/haha.png'), - -1, - 'Expected the generated skin to not have "http://www.google.ca/haha.png" replaced by anything' - ); - - // Verify /test/color was not replaced - assert.notStrictEqual( - css.indexOf('/test/color'), - -1, - 'Did not expected the generated skin to replace "/test/color"' - ); - assert.strictEqual( - css.indexOf('/test/target/color'), - -1, - 'Did not expected the generated skin to replace "/test/color"' - ); - - // Mingle with the spacing to make sure we're somewhat robust for user input - skinConfig = { 'institutional-logo-url': " ' /test/directory ' " }; - UITestUtil.updateSkinAndWait( - globalAdminRestContext, - testTenantAlias, - skinConfig, - err => { - assert.ok(!err); - - RestAPI.UI.getSkin(testTenantAdminRestContext, (err, css, response) => { - assert.ok(!err); - - // Verify /test/directory was replaced, it is ok if we lost the excessive space - assert.strictEqual( - css.indexOf('/test/directory'), - -1, - 'Expected the generated skin to have "/test/directory" replaced by the mapping' - ); - assert.notStrictEqual( - css.indexOf('/test/target/directory'), - -1, - 'Expected the generated skin to have "/test/directory" replaced by the mapping' - ); - return callback(); - }); - } - ); + RestAPI.UI.getSkin(testTenantAdminRestContext, (err, css, response) => { + assert.ok(!err); + + // Verify /test/directory was replaced, it is ok if we lost the excessive space + assert.strictEqual( + css.indexOf('/test/directory'), + -1, + 'Expected the generated skin to have "/test/directory" replaced by the mapping' + ); + assert.notStrictEqual( + css.indexOf('/test/target/directory'), + -1, + 'Expected the generated skin to have "/test/directory" replaced by the mapping' + ); + return callback(); + }); }); - } - ); + }); + }); }); }); }); @@ -645,44 +604,37 @@ describe('UI', () => { // Ensure that the base skin values are not rendered with // dynamic values - RestAPI.UI.getSkinVariables( - globalAdminRestContext, - testTenantAlias, - (err, variables) => { + RestAPI.UI.getSkinVariables(globalAdminRestContext, testTenantAlias, (err, variables) => { + assert.ok(!err); + + const institutionalLogoUrlValue = _getSkinVariableValue('institutional-logo-url', variables); + assert.strictEqual( + institutionalLogoUrlValue, + // eslint-disable-next-line no-template-curly-in-string + "'/assets/${tenantAlias}/logo/${tenantAlias}.png'" + ); + + // Get the rendered skin and ensure the tenant alias is + // placed in the institutional logo url + RestAPI.UI.getSkin(testTenantAdminRestContext, (err, css) => { assert.ok(!err); - const institutionalLogoUrlValue = _getSkinVariableValue( - 'institutional-logo-url', - variables - ); - assert.strictEqual( - institutionalLogoUrlValue, - // eslint-disable-next-line no-template-curly-in-string - "'/assets/${tenantAlias}/logo/${tenantAlias}.png'" + // Ensure the `.oae-institutiona-logo` class + // contains the dynamic value + const expectedInstitutionalLogoStr = util.format( + '.oae-institutional-logo{background-image:url(/assets/%s/logo/%s.png)}', + testTenantAlias, + testTenantAlias ); + assert.notStrictEqual(css.indexOf(expectedInstitutionalLogoStr), -1); - // Get the rendered skin and ensure the tenant alias is - // placed in the institutional logo url - RestAPI.UI.getSkin(testTenantAdminRestContext, (err, css) => { - assert.ok(!err); - - // Ensure the `.oae-institutiona-logo` class - // contains the dynamic value - const expectedInstitutionalLogoStr = util.format( - '.oae-institutional-logo{background-image:url(/assets/%s/logo/%s.png)}', - testTenantAlias, - testTenantAlias - ); - assert.notStrictEqual(css.indexOf(expectedInstitutionalLogoStr), -1); - - RestAPI.UI.getLogo(testTenantAdminRestContext, (err, logoURL) => { - // Ensure the logo we're getting is the same as fetched in the CSS above - assert.notStrictEqual(expectedInstitutionalLogoStr.indexOf(logoURL), -1); - return callback(); - }); + RestAPI.UI.getLogo(testTenantAdminRestContext, (err, logoURL) => { + // Ensure the logo we're getting is the same as fetched in the CSS above + assert.notStrictEqual(expectedInstitutionalLogoStr.indexOf(logoURL), -1); + return callback(); }); - } - ); + }); + }); }); } ); diff --git a/packages/oae-uservoice/lib/api.js b/packages/oae-uservoice/lib/api.js index 5bc14713cc..76a2d23ee9 100644 --- a/packages/oae-uservoice/lib/api.js +++ b/packages/oae-uservoice/lib/api.js @@ -19,7 +19,7 @@ const UservoiceSSO = require('uservoice-sso'); const log = require('oae-logger').logger('oae-uservoice-api'); const OaeUtil = require('oae-util/lib/util'); -const UservoiceConfig = require('oae-config').config('oae-uservoice'); +const UservoiceConfig = require('oae-config').setUpConfig('oae-uservoice'); const UservoiceProfile = require('./internal/profile'); const TIME_FIVE_MINUTES_IN_SECONDS = 15 * 60; diff --git a/packages/oae-uservoice/lib/internal/profile.js b/packages/oae-uservoice/lib/internal/profile.js index 7a2db74709..a904ad563e 100644 --- a/packages/oae-uservoice/lib/internal/profile.js +++ b/packages/oae-uservoice/lib/internal/profile.js @@ -14,7 +14,7 @@ */ const { AuthzConstants } = require('oae-authz/lib/constants'); -const PrincipalsConfig = require('oae-config').config('oae-principals'); +const PrincipalsConfig = require('oae-config').setUpConfig('oae-principals'); const UservoiceLocales = require('./locales'); diff --git a/packages/oae-util/lib/modules.js b/packages/oae-util/lib/modules.js index 3f9f602275..0d69b11ed6 100644 --- a/packages/oae-util/lib/modules.js +++ b/packages/oae-util/lib/modules.js @@ -13,18 +13,47 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const async = require('async'); -const _ = require('underscore'); +import fs from 'fs'; +import async from 'async'; +import _ from 'underscore'; -const log = require('oae-logger').logger('oae-modules'); -const OaeUtil = require('oae-util/lib/util'); -const IO = require('./io'); -const Swagger = require('./swagger'); +import { logger } from 'oae-logger'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as IO from './io'; +import Swagger from './swagger'; + +const log = logger('oae-modules'); // Variable that will be used to cache the available modules let cachedAvailableModules = []; +// The ES6 modules so far +const ES6Modules = [ + 'oae-version', + 'oae-doc', + 'oae-logger', + 'oae-config', + 'oae-ui', + 'oae-lti', + 'oae-emitter', + 'oae-telemetry', + 'oae-activity', + 'oae-authentication', + 'oae-authz', + 'oae-content', + 'oae-discussions', + 'oae-email', + 'oae-folders', + 'oae-following', + 'oae-jitsi', + 'oae-library', + 'oae-messagebox', + 'oae-tincanapi', + 'oae-preview-processor', + 'oae-search', + 'oae-tenants' +]; + /// /////////////////////// // Module bootstrapping // /// /////////////////////// @@ -77,19 +106,26 @@ const bootstrapModulesInit = function(modules, config, callback) { async.mapSeries( modules, (moduleName, done) => { + const _onceDone = err => { + if (err) { + log().error(err.stack); + log().error({ err }, 'Error initializing module %s', moduleName); + return callback(err); + } + + log().info('Initialized module %s', moduleName); + done(); + }; + const moduleInitPath = OaeUtil.getNodeModulesDir() + moduleName + MODULE_INIT_FILE; if (fs.existsSync(moduleInitPath)) { - require(moduleName + MODULE_INIT_FILE)(config, err => { - if (err) { - log().error(err.stack); - log().error({ err }, 'Error initializing module %s', moduleName); - return callback(err); - } - - log().info('Initialized module %s', moduleName); - done(); - }); + // ES6 modules cannot have an export default as a function, so init it exported instead + if (_.contains(ES6Modules, moduleName)) { + require(moduleName + MODULE_INIT_FILE).init(config, _onceDone); + } else { + require(moduleName + MODULE_INIT_FILE)(config, _onceDone); + } } else { done(); } @@ -185,8 +221,4 @@ const getAvailableModules = function() { return cachedAvailableModules.slice(0); }; -module.exports = { - initAvailableModules, - getAvailableModules, - bootstrapModules -}; +export { initAvailableModules, getAvailableModules, bootstrapModules }; diff --git a/packages/oae-util/lib/redis.js b/packages/oae-util/lib/redis.js index 7648c8f0ee..f7e83d563b 100644 --- a/packages/oae-util/lib/redis.js +++ b/packages/oae-util/lib/redis.js @@ -118,13 +118,19 @@ const getClient = function() { * @param {Object} callback.err An error that occurred, if any */ const flush = function(callback) { - client.flushdb([], err => { + const done = err => { if (err) { return callback({ code: 500, msg: err }); } callback(); - }); + }; + + if (client) { + client.flushdb([], done); + } else { + done('Unable to flush redis. Try initializing it first.'); + } }; module.exports = { diff --git a/packages/oae-version/lib/api.js b/packages/oae-version/lib/api.js index c67886cb5c..a2ebc86b0b 100644 --- a/packages/oae-version/lib/api.js +++ b/packages/oae-version/lib/api.js @@ -14,11 +14,10 @@ * permissions and limitations under the License. */ -const path = require('path'); -const { Map } = require('immutable'); -const git = require('nodegit'); - -const _ = require('underscore'); +import path from 'path'; +import { Map } from 'immutable'; +import git from 'nodegit'; +import _ from 'underscore'; // A variable that will hold the path to the UI directory const hilaryDirectory = path.resolve(__dirname, '..', '..', '..'); @@ -78,4 +77,4 @@ const getVersion = async function(repoPath = hilaryDirectory, repoInformation = return repoInformation; }; -module.exports = { getVersion, getVersionCB }; +export { getVersion, getVersionCB }; diff --git a/packages/oae-version/lib/init.js b/packages/oae-version/lib/init.js index bf19b26910..c506764388 100644 --- a/packages/oae-version/lib/init.js +++ b/packages/oae-version/lib/init.js @@ -21,6 +21,6 @@ * @returns {Object} function the callback from parameters * @see {oae-util/lib/oae} */ -module.exports = function(config, callback) { +export const init = function(config, callback) { return callback(); }; diff --git a/packages/oae-version/lib/rest.js b/packages/oae-version/lib/rest.js index 58bed8ac6b..c10091a59b 100644 --- a/packages/oae-version/lib/rest.js +++ b/packages/oae-version/lib/rest.js @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -const VersionAPI = require('./api'); -const OAE = require('oae-util/lib/oae'); +import * as OAE from 'oae-util/lib/oae'; +import * as VersionAPI from './api'; const _getVersion = async function(req, res) { try { diff --git a/packages/oae-version/tests/test-version.js b/packages/oae-version/tests/test-version.js index 6492fac6de..627cbc8816 100644 --- a/packages/oae-version/tests/test-version.js +++ b/packages/oae-version/tests/test-version.js @@ -13,13 +13,11 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); - -const assert = require('assert'); -const { fromJS } = require('immutable'); - -const RestAPI = require('oae-rest'); -const TestsUtil = require('oae-tests'); +import assert from 'assert'; +import _ from 'underscore'; +import { fromJS } from 'immutable'; +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; describe('Git information', function() { /** From 90447f52d5715e32487e1db1d4860d029d443ff8 Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Wed, 17 Apr 2019 15:34:25 +0100 Subject: [PATCH 16/21] chore: update restjsdoc submodule --- packages/restjsdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/restjsdoc b/packages/restjsdoc index b8d6c0a83a..31eaaa12fe 160000 --- a/packages/restjsdoc +++ b/packages/restjsdoc @@ -1 +1 @@ -Subproject commit b8d6c0a83a9410e9c02f49f3aa9e189718f1e57a +Subproject commit 31eaaa12fed01279be8ba068a68343598ea1e07f From 6135f14f84b8876c88c503bfea7fced0d3ff967a Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Thu, 18 Apr 2019 11:07:07 +0100 Subject: [PATCH 17/21] refactor: move to es6 modules --- .../lib/disable-users-by-tenancy.js | 53 ++--- packages/oae-activity/lib/internal/buckets.js | 2 +- .../lib/internal/notifications.js | 8 +- packages/oae-authentication/lib/api.js | 2 +- .../lib/strategies/local/init.js | 2 +- .../tests/test-auth-local.js | 4 +- .../oae-authentication/tests/test-auth.js | 2 +- .../oae-authentication/tests/test-cookies.js | 2 +- .../tests/test-external-strategies.js | 4 +- .../oae-authentication/tests/test-oauth.js | 2 +- .../tests/test-shibboleth-migration.js | 7 +- .../oae-authentication/tests/test-signed.js | 2 +- packages/oae-config/lib/api.js | 6 +- packages/oae-discussions/lib/migration.js | 4 +- packages/oae-folders/lib/api.js | 2 +- packages/oae-folders/lib/test/util.js | 2 +- packages/oae-following/lib/principals.js | 2 +- packages/oae-lti/lib/internal/dao.js | 2 +- packages/oae-lti/tests/test-lti.js | 2 +- .../tests/test-previews.js | 2 +- packages/oae-principals/lib/activity.js | 121 +++++----- packages/oae-principals/lib/api.group.js | 52 +++-- packages/oae-principals/lib/api.js | 19 +- packages/oae-principals/lib/api.picture.js | 113 ++++------ .../lib/api.termsAndConditions.js | 19 +- packages/oae-principals/lib/api.user.js | 75 ++++--- packages/oae-principals/lib/constants.js | 4 +- packages/oae-principals/lib/delete.js | 45 ++-- packages/oae-principals/lib/init.js | 17 +- packages/oae-principals/lib/internal/dao.js | 36 +-- .../oae-principals/lib/internal/emitter.js | 4 +- packages/oae-principals/lib/invitations.js | 24 +- .../oae-principals/lib/libraries/members.js | 78 ++++--- .../lib/libraries/memberships.js | 32 +-- packages/oae-principals/lib/migration.js | 8 +- packages/oae-principals/lib/model.group.js | 8 +- packages/oae-principals/lib/model.js | 8 +- packages/oae-principals/lib/model.user.js | 10 +- packages/oae-principals/lib/rest.group.js | 17 +- packages/oae-principals/lib/rest.js | 49 ++--- packages/oae-principals/lib/rest.user.js | 72 +++--- packages/oae-principals/lib/restmodel.js | 2 - packages/oae-principals/lib/search.js | 52 ++--- packages/oae-principals/lib/test/util.js | 45 ++-- packages/oae-principals/lib/util.js | 53 ++--- .../oae-principals/tests/test-activity.js | 2 +- .../oae-principals/tests/test-export-data.js | 2 +- packages/oae-principals/tests/test-groups.js | 2 +- packages/oae-principals/tests/test-users.js | 2 +- packages/oae-rest | 2 +- packages/oae-tests/lib/util.js | 2 +- packages/oae-util/lib/cassandra.js | 24 +- packages/oae-util/lib/cleaner.js | 27 +-- packages/oae-util/lib/counter.js | 4 +- packages/oae-util/lib/emitter.js | 4 +- packages/oae-util/lib/image.js | 121 +++++----- packages/oae-util/lib/init.js | 33 +-- packages/oae-util/lib/internal/globals.js | 2 +- .../lib/internal/globals/underscore.js | 3 +- packages/oae-util/lib/internal/shutdown.js | 16 +- packages/oae-util/lib/io.js | 19 +- packages/oae-util/lib/locking.js | 20 +- packages/oae-util/lib/middleware/multipart.js | 19 +- packages/oae-util/lib/modules.js | 6 +- packages/oae-util/lib/mq.js | 108 ++++----- packages/oae-util/lib/oae.js | 14 +- packages/oae-util/lib/pubsub.js | 16 +- packages/oae-util/lib/redis.js | 13 +- packages/oae-util/lib/sanitization.js | 10 +- packages/oae-util/lib/server.js | 31 ++- packages/oae-util/lib/signature.js | 15 +- packages/oae-util/lib/swagger.js | 36 ++- packages/oae-util/lib/swaggerParamTypes.js | 18 +- packages/oae-util/lib/taskqueue.js | 32 +-- packages/oae-util/lib/tempfile.js | 101 +++++---- packages/oae-util/lib/test/mq-util.js | 10 +- packages/oae-util/lib/tz.js | 21 +- packages/oae-util/lib/util.js | 25 +-- packages/oae-util/lib/validator.js | 22 +- packages/oae-util/tests/test-cassandra.js | 42 ++-- packages/oae-util/tests/test-cleaner.js | 19 +- packages/oae-util/tests/test-globals.js | 4 +- packages/oae-util/tests/test-image.js | 207 ++++++++---------- packages/oae-util/tests/test-io.js | 8 +- packages/oae-util/tests/test-locking.js | 5 +- packages/oae-util/tests/test-mq.js | 127 ++++++----- packages/oae-util/tests/test-pubsub.js | 4 +- packages/oae-util/tests/test-sanitization.js | 4 +- packages/oae-util/tests/test-server.js | 27 ++- packages/oae-util/tests/test-signature.js | 30 +-- packages/oae-util/tests/test-swagger.js | 95 ++------ packages/oae-util/tests/test-taskqueue.js | 4 +- packages/oae-util/tests/test-tz.js | 9 +- packages/oae-util/tests/test-util.js | 16 +- packages/oae-util/tests/test-validator.js | 22 +- packages/restjsdoc | 2 +- 96 files changed, 1143 insertions(+), 1339 deletions(-) diff --git a/etc/migration/disable_users_from_tenancy/lib/disable-users-by-tenancy.js b/etc/migration/disable_users_from_tenancy/lib/disable-users-by-tenancy.js index bab9d2fa35..ae22a489ee 100644 --- a/etc/migration/disable_users_from_tenancy/lib/disable-users-by-tenancy.js +++ b/etc/migration/disable_users_from_tenancy/lib/disable-users-by-tenancy.js @@ -1,33 +1,36 @@ /*! -* Copyright 2017 Apereo Foundation (AF) Licensed under the -* Educational Community License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may -* obtain a copy of the License at -* -* http://opensource.org/licenses/ECL-2.0 -* -* Unless required by applicable law or agreed to in writing, -* software distributed under the License is distributed on an "AS IS" -* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -* or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ + * Copyright 2017 Apereo Foundation (AF) Licensed under the + * Educational Community License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://opensource.org/licenses/ECL-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ /* -* Disable users belonging to a disabled tenancy -* Github issue #1304 -*/ + * Disable users belonging to a disabled tenancy + * Github issue #1304 + */ /* eslint-disable */ -const path = require('path'); -const util = require('util'); +import PrincipalsAPI from 'oae-principals'; + +import path from 'path'; +import util from 'util'; +import {logger} from 'oae-logger' + +import { User } from 'oae-principals/lib/model'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { Context } from 'oae-context'; -const { AuthzConstants } = require('oae-authz/lib/constants'); -const { Context } = require('oae-context'); -const log = require('oae-logger').logger('oae-script-main'); -const PrincipalsAPI = require('oae-principals'); -const TenantsAPI = require('oae-tenants'); -const { User } = require('oae-principals/lib/model'); +import * as TenantsAPI from 'oae-tenants'; +const log = logger('oae-script-main'); /** * Disable users from the system by updating the deleted flag * @@ -69,6 +72,6 @@ const doMigration = function(ctx, tenantAlias, disabled, callback) { } }; -module.exports = { +export { doMigration }; diff --git a/packages/oae-activity/lib/internal/buckets.js b/packages/oae-activity/lib/internal/buckets.js index fe90063ad0..d34ade0da0 100644 --- a/packages/oae-activity/lib/internal/buckets.js +++ b/packages/oae-activity/lib/internal/buckets.js @@ -19,7 +19,7 @@ import _ from 'underscore'; import * as Locking from 'oae-util/lib/locking'; import { logger } from 'oae-logger'; -import OAE from 'oae-util/lib/oae'; +import * as OAE from 'oae-util/lib/oae'; import * as TelemetryAPI from 'oae-telemetry'; const log = logger('oae-activity-buckets'); diff --git a/packages/oae-activity/lib/internal/notifications.js b/packages/oae-activity/lib/internal/notifications.js index ce9a418182..4f21799cbe 100644 --- a/packages/oae-activity/lib/internal/notifications.js +++ b/packages/oae-activity/lib/internal/notifications.js @@ -18,16 +18,16 @@ import _ from 'underscore'; import Counter from 'oae-util/lib/counter'; import { logger } from 'oae-logger'; import { PrincipalsConstants } from 'oae-principals/lib/constants'; -import PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; import { ActivityConstants } from 'oae-activity/lib/constants'; import * as ActivityUtil from 'oae-activity/lib/util'; import ActivityEmitter from './emitter'; -import * as ActivityDAO from './dao' -import * as ActivityAggregator from './aggregator' +import * as ActivityDAO from './dao'; +import * as ActivityAggregator from './aggregator'; -const log = logger('oae-activity-notifications');; +const log = logger('oae-activity-notifications'); // Tracks the handling of notifications for synchronization to determine when there are no // notifications being processed diff --git a/packages/oae-authentication/lib/api.js b/packages/oae-authentication/lib/api.js index 1bf6faf6ce..541649949e 100644 --- a/packages/oae-authentication/lib/api.js +++ b/packages/oae-authentication/lib/api.js @@ -27,7 +27,7 @@ import * as Locking from 'oae-util/lib/locking'; import * as EmailAPI from 'oae-email'; import OaeEmitter from 'oae-util/lib/emitter'; import * as OaeUtil from 'oae-util/lib/util'; -import * as PrincipalsAPI from 'oae-principals'; +import PrincipalsAPI from 'oae-principals'; import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; import * as TenantsAPI from 'oae-tenants'; import * as TenantsUtil from 'oae-tenants/lib/util'; diff --git a/packages/oae-authentication/lib/strategies/local/init.js b/packages/oae-authentication/lib/strategies/local/init.js index a3761e121c..4bafb7677f 100644 --- a/packages/oae-authentication/lib/strategies/local/init.js +++ b/packages/oae-authentication/lib/strategies/local/init.js @@ -18,7 +18,7 @@ import passport from 'passport'; import * as ConfigAPI from 'oae-config'; import { Context } from 'oae-context'; -import * as PrincipalsAPI from 'oae-principals'; +import PrincipalsAPI from 'oae-principals'; import { User } from 'oae-principals/lib/model'; import * as AuthenticationAPI from 'oae-authentication'; diff --git a/packages/oae-authentication/tests/test-auth-local.js b/packages/oae-authentication/tests/test-auth-local.js index ce192b6672..f904350431 100644 --- a/packages/oae-authentication/tests/test-auth-local.js +++ b/packages/oae-authentication/tests/test-auth-local.js @@ -15,11 +15,11 @@ import assert from 'assert'; -import Cassandra from 'oae-util/lib/cassandra'; +import * as Cassandra from 'oae-util/lib/cassandra'; import * as ConfigTestUtil from 'oae-config/lib/test/util'; import { Context } from 'oae-context'; import PrincipalsAPI from 'oae-principals'; -import RestAPI from 'oae-rest'; +import * as RestAPI from 'oae-rest'; import { RestContext } from 'oae-rest/lib/model'; import * as TestsUtil from 'oae-tests'; diff --git a/packages/oae-authentication/tests/test-auth.js b/packages/oae-authentication/tests/test-auth.js index 7d226ee280..8029599190 100644 --- a/packages/oae-authentication/tests/test-auth.js +++ b/packages/oae-authentication/tests/test-auth.js @@ -15,7 +15,7 @@ import assert from 'assert'; -import RestAPI from 'oae-rest'; +import * as RestAPI from 'oae-rest'; import * as TestsUtil from 'oae-tests'; describe('Authentication', () => { diff --git a/packages/oae-authentication/tests/test-cookies.js b/packages/oae-authentication/tests/test-cookies.js index c3675ea3be..3e40e8aa67 100644 --- a/packages/oae-authentication/tests/test-cookies.js +++ b/packages/oae-authentication/tests/test-cookies.js @@ -16,7 +16,7 @@ import assert from 'assert'; import _ from 'underscore'; -import RestAPI from 'oae-rest'; +import * as RestAPI from 'oae-rest'; import * as TestsUtil from 'oae-tests'; describe('Authentication', () => { diff --git a/packages/oae-authentication/tests/test-external-strategies.js b/packages/oae-authentication/tests/test-external-strategies.js index f63cfdf392..73bde8a5cd 100644 --- a/packages/oae-authentication/tests/test-external-strategies.js +++ b/packages/oae-authentication/tests/test-external-strategies.js @@ -23,8 +23,8 @@ import request from 'request'; import * as ConfigTestUtil from 'oae-config/lib/test/util'; import { Cookie } from 'tough-cookie'; -import PrincipalsTestUtil from 'oae-principals/lib/test/util'; -import RestAPI from 'oae-rest'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import * as RestAPI from 'oae-rest'; import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; import * as TestsUtil from 'oae-tests'; diff --git a/packages/oae-authentication/tests/test-oauth.js b/packages/oae-authentication/tests/test-oauth.js index 6fb947cea0..e56f68894c 100644 --- a/packages/oae-authentication/tests/test-oauth.js +++ b/packages/oae-authentication/tests/test-oauth.js @@ -17,7 +17,7 @@ import assert from 'assert'; import OAuth from 'oauth'; import * as ConfigTestUtil from 'oae-config/lib/test/util'; -import RestAPI from 'oae-rest'; +import * as RestAPI from 'oae-rest'; import { RestContext } from 'oae-rest/lib/model'; import * as TestsUtil from 'oae-tests'; diff --git a/packages/oae-authentication/tests/test-shibboleth-migration.js b/packages/oae-authentication/tests/test-shibboleth-migration.js index 16b850397a..1695c082ae 100644 --- a/packages/oae-authentication/tests/test-shibboleth-migration.js +++ b/packages/oae-authentication/tests/test-shibboleth-migration.js @@ -19,8 +19,8 @@ import _ from 'underscore'; import csv from 'csv'; import temp from 'temp'; -import Cassandra from 'oae-util/lib/cassandra'; -import RestAPI from 'oae-rest'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as RestAPI from 'oae-rest'; import * as TestsUtil from 'oae-tests'; import { logger } from 'oae-logger'; import ShibbolethMigrator from '../../../etc/migration/shibboleth_migration/migrate-users-to-shibboleth.js'; @@ -141,7 +141,8 @@ describe('Shibboleth Migration', () => { .map(googleLoginId => { return [ { - query: 'INSERT INTO "AuthenticationUserLoginId" ("loginId", "userId", "value") VALUES (?, ?, ?)', + query: + 'INSERT INTO "AuthenticationUserLoginId" ("loginId", "userId", "value") VALUES (?, ?, ?)', parameters: [googleLoginId.loginId, googleLoginId.userId, '1'] }, { diff --git a/packages/oae-authentication/tests/test-signed.js b/packages/oae-authentication/tests/test-signed.js index 29dd947552..bce3103d59 100644 --- a/packages/oae-authentication/tests/test-signed.js +++ b/packages/oae-authentication/tests/test-signed.js @@ -18,7 +18,7 @@ import url from 'url'; import util from 'util'; import _ from 'underscore'; -import RestAPI from 'oae-rest'; +import * as RestAPI from 'oae-rest'; import { RestContext } from 'oae-rest/lib/model'; import * as TestsUtil from 'oae-tests'; diff --git a/packages/oae-config/lib/api.js b/packages/oae-config/lib/api.js index 16ac565a53..c8167e31b1 100644 --- a/packages/oae-config/lib/api.js +++ b/packages/oae-config/lib/api.js @@ -18,9 +18,9 @@ import _ from 'underscore'; import clone from 'clone'; import * as EmitterAPI from 'oae-emitter'; -import IO from 'oae-util/lib/io'; -import OaeUtil from 'oae-util/lib/util'; -import Pubsub from 'oae-util/lib/pubsub'; +import * as IO from 'oae-util/lib/io'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as Pubsub from 'oae-util/lib/pubsub'; import { Validator } from 'oae-util/lib/validator'; import { logger } from 'oae-logger'; diff --git a/packages/oae-discussions/lib/migration.js b/packages/oae-discussions/lib/migration.js index a2fe5adfc8..db15897f9c 100644 --- a/packages/oae-discussions/lib/migration.js +++ b/packages/oae-discussions/lib/migration.js @@ -1,4 +1,4 @@ -import Cassandra from 'oae-util/lib/cassandra'; +import { createColumnFamilies } from 'oae-util/lib/cassandra'; /** * Ensure that the all of the discussion schemas are created. If they already exist, this method will not do anything @@ -8,7 +8,7 @@ import Cassandra from 'oae-util/lib/cassandra'; * @api private */ const ensureSchema = function(callback) { - Cassandra.createColumnFamilies( + createColumnFamilies( { Discussions: 'CREATE TABLE "Discussions" ("id" text PRIMARY KEY, "tenantAlias" text, "displayName" text, "visibility" text, "description" text, "createdBy" text, "created" text, "lastModified" text)' diff --git a/packages/oae-folders/lib/api.js b/packages/oae-folders/lib/api.js index 503e861d80..09fb4a40f6 100644 --- a/packages/oae-folders/lib/api.js +++ b/packages/oae-folders/lib/api.js @@ -29,7 +29,7 @@ import * as LibraryAPI from 'oae-library'; import * as MessageBoxAPI from 'oae-messagebox'; import * as OaeUtil from 'oae-util/lib/util'; -import * as PrincipalsAPI from 'oae-principals'; +import PrincipalsAPI from 'oae-principals'; import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; import * as PrincipalsUtil from 'oae-principals/lib/util'; import * as ResourceActions from 'oae-resource/lib/actions'; diff --git a/packages/oae-folders/lib/test/util.js b/packages/oae-folders/lib/test/util.js index e3d0b48428..c5bfef105e 100644 --- a/packages/oae-folders/lib/test/util.js +++ b/packages/oae-folders/lib/test/util.js @@ -26,7 +26,7 @@ import * as LibraryAPI from 'oae-library'; import * as LibraryTestUtil from 'oae-library/lib/test/util'; import * as MQTestUtil from 'oae-util/lib/test/mq-util'; import PreviewConstants from 'oae-preview-processor/lib/constants'; -import * as PrincipalsAPI from 'oae-principals'; +import PrincipalsAPI from 'oae-principals'; import * as RestAPI from 'oae-rest'; import * as SearchTestUtil from 'oae-search/lib/test/util'; import * as FoldersLibrary from 'oae-folders/lib/library'; diff --git a/packages/oae-following/lib/principals.js b/packages/oae-following/lib/principals.js index 58d73677e0..6d5eaf451d 100644 --- a/packages/oae-following/lib/principals.js +++ b/packages/oae-following/lib/principals.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -import * as PrincipalsAPI from 'oae-principals'; +import PrincipalsAPI from 'oae-principals'; import * as FollowingAuthz from 'oae-following/lib/authz'; import * as FollowingDAO from 'oae-following/lib/internal/dao'; diff --git a/packages/oae-lti/lib/internal/dao.js b/packages/oae-lti/lib/internal/dao.js index 0abc8c663a..3572f67c05 100644 --- a/packages/oae-lti/lib/internal/dao.js +++ b/packages/oae-lti/lib/internal/dao.js @@ -15,7 +15,7 @@ import _ from 'underscore'; -import Cassandra from 'oae-util/lib/cassandra'; +import * as Cassandra from 'oae-util/lib/cassandra'; import { LtiTool } from 'oae-lti/lib/model'; /** diff --git a/packages/oae-lti/tests/test-lti.js b/packages/oae-lti/tests/test-lti.js index 14e5b880de..d400ef5705 100644 --- a/packages/oae-lti/tests/test-lti.js +++ b/packages/oae-lti/tests/test-lti.js @@ -20,7 +20,7 @@ import _ from 'underscore'; import * as RestAPI from 'oae-rest'; import * as TestsUtil from 'oae-tests'; -import * as PrincipalsAPI from 'oae-principals'; +import PrincipalsAPI from 'oae-principals'; import { User } from 'oae-principals/lib/model.user'; describe('LTI tools', () => { diff --git a/packages/oae-preview-processor/tests/test-previews.js b/packages/oae-preview-processor/tests/test-previews.js index 836924b498..f69192883d 100644 --- a/packages/oae-preview-processor/tests/test-previews.js +++ b/packages/oae-preview-processor/tests/test-previews.js @@ -1713,7 +1713,7 @@ describe('Preview processor', () => { } _createContentAndWait('collabdoc', null, null, (restCtx, content) => { - setTimeout(assert.strictEqual, 200, content.previews.status, 'done'); + setTimeout(assert.strictEqual, 500, content.previews.status, 'done'); // Ensure we have a thumbnail url. assert.strictEqual(content.previews.thumbnailUrl.indexOf('/api/download/signed'), 0); _verifySignedUriDownload(restCtx, content.previews.thumbnailUrl, () => { diff --git a/packages/oae-principals/lib/activity.js b/packages/oae-principals/lib/activity.js index 24e5d8a96d..1659f8b24c 100644 --- a/packages/oae-principals/lib/activity.js +++ b/packages/oae-principals/lib/activity.js @@ -13,18 +13,17 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const ActivityAPI = require('oae-activity'); -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityModel = require('oae-activity/lib/model'); -const ActivityUtil = require('oae-activity/lib/util'); - -const PrincipalsAPI = require('oae-principals'); -const { PrincipalsConstants } = require('oae-principals/lib/constants'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const PrincipalsUtil = require('oae-principals/lib/util'); +import * as ActivityAPI from 'oae-activity'; +import * as ActivityModel from 'oae-activity/lib/model'; +import * as ActivityUtil from 'oae-activity/lib/util'; +import { emitter } from 'oae-principals'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; +import { ActivityConstants } from 'oae-activity/lib/constants'; +import { PrincipalsConstants } from 'oae-principals/lib/constants'; /// /////////////// // GROUP-CREATE // /// /////////////// @@ -32,7 +31,7 @@ const PrincipalsUtil = require('oae-principals/lib/util'); /*! * Fire the 'group-create' activity when a new group is created. */ -PrincipalsAPI.emitter.on( +emitter.on( PrincipalsConstants.events.CREATED_GROUP, // eslint-disable-next-line no-unused-vars (ctx, group, memberChangeInfo) => { @@ -81,7 +80,7 @@ ActivityAPI.registerActivityType(PrincipalsConstants.activity.ACTIVITY_GROUP_CRE /*! * Fire the 'group-update' or 'group-update-visibility' activity when a group is updated. */ -PrincipalsAPI.emitter.on(PrincipalsConstants.events.UPDATED_GROUP, (ctx, newGroup, oldGroup) => { +emitter.on(PrincipalsConstants.events.UPDATED_GROUP, (ctx, newGroup, oldGroup) => { const millis = Date.now(); const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { user: ctx.user() @@ -232,60 +231,57 @@ ActivityAPI.registerActivityType(PrincipalsConstants.activity.ACTIVITY_REQUEST_T /*! * Fire the group-add-member or group-update-member-role activity when someone adds members to a group or updates user roles */ -PrincipalsAPI.emitter.on( - PrincipalsConstants.events.UPDATED_GROUP_MEMBERS, - (ctx, group, oldGroup, memberChangeInfo, opts) => { - if (opts.invitation) { - // If this member update came from an invitation, we bypass adding activity as there is a - // dedicated activity for that - return; - } +emitter.on(PrincipalsConstants.events.UPDATED_GROUP_MEMBERS, (ctx, group, oldGroup, memberChangeInfo, opts) => { + if (opts.invitation) { + // If this member update came from an invitation, we bypass adding activity as there is a + // dedicated activity for that + return; + } - const addedMemberIds = _.pluck(memberChangeInfo.members.added, 'id'); - const updatedMemberIds = _.pluck(memberChangeInfo.members.updated, 'id'); + const addedMemberIds = _.pluck(memberChangeInfo.members.added, 'id'); + const updatedMemberIds = _.pluck(memberChangeInfo.members.updated, 'id'); - const millis = Date.now(); - const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { - user: ctx.user() - }); - const targetResource = new ActivityModel.ActivitySeedResource('group', group.id, { group }); + const millis = Date.now(); + const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { + user: ctx.user() + }); + const targetResource = new ActivityModel.ActivitySeedResource('group', group.id, { group }); - // Post "Add Member" activities for each new member - _.each(addedMemberIds, memberId => { - const objectResourceType = PrincipalsUtil.isGroup(memberId) ? 'group' : 'user'; - const objectResource = new ActivityModel.ActivitySeedResource(objectResourceType, memberId); - const activitySeed = new ActivityModel.ActivitySeed( - PrincipalsConstants.activity.ACTIVITY_GROUP_ADD_MEMBER, - millis, - ActivityConstants.verbs.ADD, - actorResource, - objectResource, - targetResource - ); - ActivityAPI.postActivity(ctx, activitySeed); - }); + // Post "Add Member" activities for each new member + _.each(addedMemberIds, memberId => { + const objectResourceType = PrincipalsUtil.isGroup(memberId) ? 'group' : 'user'; + const objectResource = new ActivityModel.ActivitySeedResource(objectResourceType, memberId); + const activitySeed = new ActivityModel.ActivitySeed( + PrincipalsConstants.activity.ACTIVITY_GROUP_ADD_MEMBER, + millis, + ActivityConstants.verbs.ADD, + actorResource, + objectResource, + targetResource + ); + ActivityAPI.postActivity(ctx, activitySeed); + }); - // Post "Update member role" activities for each membership update - _.each(updatedMemberIds, memberId => { - const objectResourceType = PrincipalsUtil.isGroup(memberId) ? 'group' : 'user'; - const objectResource = new ActivityModel.ActivitySeedResource(objectResourceType, memberId); - const activitySeed = new ActivityModel.ActivitySeed( - PrincipalsConstants.activity.ACTIVITY_GROUP_UPDATE_MEMBER_ROLE, - millis, - ActivityConstants.verbs.UPDATE, - actorResource, - objectResource, - targetResource - ); - ActivityAPI.postActivity(ctx, activitySeed); - }); - } -); + // Post "Update member role" activities for each membership update + _.each(updatedMemberIds, memberId => { + const objectResourceType = PrincipalsUtil.isGroup(memberId) ? 'group' : 'user'; + const objectResource = new ActivityModel.ActivitySeedResource(objectResourceType, memberId); + const activitySeed = new ActivityModel.ActivitySeed( + PrincipalsConstants.activity.ACTIVITY_GROUP_UPDATE_MEMBER_ROLE, + millis, + ActivityConstants.verbs.UPDATE, + actorResource, + objectResource, + targetResource + ); + ActivityAPI.postActivity(ctx, activitySeed); + }); +}); /*! * Fire the group-join activity when someone joins a group */ -PrincipalsAPI.emitter.on( +emitter.on( PrincipalsConstants.events.JOINED_GROUP, // eslint-disable-next-line no-unused-vars (ctx, group, oldGroup, memberChangeInfo) => { @@ -311,12 +307,7 @@ PrincipalsAPI.emitter.on( * Fire the request-group-join activity when someone wants to join a group */ // eslint-disable-next-line no-unused-vars -PrincipalsAPI.emitter.on(PrincipalsConstants.events.REQUEST_TO_JOIN_GROUP, function( - ctx, - group, - oldGroup, - memberChangeInfo -) { +emitter.on(PrincipalsConstants.events.REQUEST_TO_JOIN_GROUP, function(ctx, group, oldGroup, memberChangeInfo) { const millis = Date.now(); const actorResource = new ActivityModel.ActivitySeedResource('user', ctx.user().id, { user: ctx.user() }); const objectResource = new ActivityModel.ActivitySeedResource('group', group.id, { group }); @@ -335,7 +326,7 @@ PrincipalsAPI.emitter.on(PrincipalsConstants.events.REQUEST_TO_JOIN_GROUP, funct /*! * Fire the request-group-join activity when someone has been rejected to join a group */ -PrincipalsAPI.emitter.on( +emitter.on( PrincipalsConstants.events.REQUEST_TO_JOIN_GROUP_REJECTED, // eslint-disable-next-line no-unused-vars (ctx, group, requester) => { diff --git a/packages/oae-principals/lib/api.group.js b/packages/oae-principals/lib/api.group.js index 1145fc9fa6..f211c6d96f 100644 --- a/packages/oae-principals/lib/api.group.js +++ b/packages/oae-principals/lib/api.group.js @@ -13,29 +13,33 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); -const ShortId = require('shortid'); - -const AuthzAPI = require('oae-authz'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzInvitations = require('oae-authz/lib/invitations'); -const AuthzPermissions = require('oae-authz/lib/permissions'); -const AuthzUtil = require('oae-authz/lib/util'); -const Config = require('oae-config/lib/api').setUpConfig('oae-principals'); -const LibraryAPI = require('oae-library'); -const log = require('oae-logger').logger('oae-principals'); -const MessageBoxAPI = require('oae-messagebox'); -const OaeUtil = require('oae-util/lib/util'); -const ResourceActions = require('oae-resource/lib/actions'); -const Signature = require('oae-util/lib/signature'); -const { Validator } = require('oae-authz/lib/validator'); - -const { PrincipalsConstants } = require('./constants'); -const PrincipalsDAO = require('./internal/dao'); -const PrincipalsMembersLibrary = require('./libraries/members'); -const PrincipalsEmitter = require('./internal/emitter'); -const PrincipalsUtil = require('./util'); +import util from 'util'; +import _ from 'underscore'; +import ShortId from 'shortid'; + +import { logger } from 'oae-logger'; +import { setUpConfig } from 'oae-config'; + +import * as AuthzAPI from 'oae-authz'; +import * as AuthzInvitations from 'oae-authz/lib/invitations'; +import * as AuthzPermissions from 'oae-authz/lib/permissions'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as LibraryAPI from 'oae-library'; +import * as MessageBoxAPI from 'oae-messagebox'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as ResourceActions from 'oae-resource/lib/actions'; +import * as Signature from 'oae-util/lib/signature'; +import { Validator } from 'oae-authz/lib/validator'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as PrincipalsDAO from './internal/dao'; +import * as PrincipalsMembersLibrary from './libraries/members'; +import PrincipalsEmitter from './internal/emitter'; +import * as PrincipalsUtil from './util'; + +import { PrincipalsConstants } from './constants'; + +const log = logger('oae-principals'); +const Config = setUpConfig('oae-principals'); /** * Get the basic profile for a group. @@ -1327,7 +1331,7 @@ const notifyOfJoinRequestDecision = function(ctx, joinRequest, callback) { }); }; -module.exports = { +export { getGroup, getFullGroupProfile, getMembersLibrary, diff --git a/packages/oae-principals/lib/api.js b/packages/oae-principals/lib/api.js index 2089f96a63..24f476f86c 100644 --- a/packages/oae-principals/lib/api.js +++ b/packages/oae-principals/lib/api.js @@ -13,12 +13,12 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const GroupAPI = require('./api.group'); -const PictureAPI = require('./api.picture'); -const TermsAndConditionsAPI = require('./api.termsAndConditions'); -const UserAPI = require('./api.user'); +import * as GroupAPI from './api.group'; +import * as PictureAPI from './api.picture'; +import * as TermsAndConditionsAPI from './api.termsAndConditions'; +import * as UserAPI from './api.user'; /** * ### Events @@ -38,10 +38,13 @@ const UserAPI = require('./api.user'); * * `joinedGroup(ctx, group, role)`: A user joined a group. The `ctx`, `group` object that was joined, and the `role` of the user that they joined as. * * `leftGroup(ctx, group, role)`: A user left a group. The `ctx` and `group` object that was left are provided as well as the `role` they had before they left. */ -const PrincipalsAPI = require('./internal/emitter'); +import PrincipalsAPI from './internal/emitter'; -module.exports = { emitter: PrincipalsAPI }; +const allExports = {}; +export default _.extend(allExports, GroupAPI, PictureAPI, TermsAndConditionsAPI, UserAPI); + +export { PrincipalsAPI as emitter }; // This file would become unmaintainable if all the logic would be placed here. // That's why we split them up in a couple of files of which the api logic gets exported. -_.extend(module.exports, GroupAPI, PictureAPI, TermsAndConditionsAPI, UserAPI); +// _.extend(module.exports, GroupAPI, PictureAPI, TermsAndConditionsAPI, UserAPI); diff --git a/packages/oae-principals/lib/api.picture.js b/packages/oae-principals/lib/api.picture.js index 4687d4aa9f..53948e5303 100644 --- a/packages/oae-principals/lib/api.picture.js +++ b/packages/oae-principals/lib/api.picture.js @@ -13,22 +13,25 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const util = require('util'); -const _ = require('underscore'); -const mime = require('mime'); - -const AuthzPermissions = require('oae-authz/lib/permissions'); -const ContentUtil = require('oae-content/lib/internal/util'); -const ImageUtil = require('oae-util/lib/image'); -const log = require('oae-logger').logger('oae-principals-shared'); -const { Validator } = require('oae-util/lib/validator'); - -const GroupAPI = require('./api.group'); -const { PrincipalsConstants } = require('./constants'); -const PrincipalsDAO = require('./internal/dao'); -const PrincipalsEmitter = require('./internal/emitter'); -const PrincipalsUtil = require('./util'); +import fs from 'fs'; +import util from 'util'; +import _ from 'underscore'; +import mime from 'mime'; + +import { logger } from 'oae-logger'; + +import * as AuthzPermissions from 'oae-authz/lib/permissions'; +import * as ContentUtil from 'oae-content/lib/internal/util'; +import * as ImageUtil from 'oae-util/lib/image'; +import { Validator } from 'oae-util/lib/validator'; +import * as GroupAPI from './api.group'; +import * as PrincipalsDAO from './internal/dao'; +import PrincipalsEmitter from './internal/emitter'; +import * as PrincipalsUtil from './util'; + +import { PrincipalsConstants } from './constants'; + +const log = logger('oae-principals-shared'); /** * Store the large picture for a principal that can be re-used later on @@ -57,17 +60,14 @@ const storePicture = function(ctx, principalId, file, callback) { validator .check(null, { code: 401, msg: 'You have to be logged in to be able to update a picture' }) .isLoggedInUser(ctx); - validator - .check(principalId, { code: 400, msg: 'A principal ID must be provided' }) - .isPrincipalId(); + validator.check(principalId, { code: 400, msg: 'A principal ID must be provided' }).isPrincipalId(); validator.check(file, { code: 400, msg: 'A file must be provided' }).notNull(); if (file) { validator.check(file.size, { code: 400, msg: 'Missing size on the file object.' }).notEmpty(); - validator - .check(file.size, { code: 400, msg: 'The size of a picture has an upper limit of 10MB.' }) - .max(10485760); + validator.check(file.size, { code: 400, msg: 'The size of a picture has an upper limit of 10MB.' }).max(10485760); validator.check(file.name, { code: 400, msg: 'Missing name on the file object.' }).notEmpty(); } + if (validator.hasErrors()) { return _cleanupOnError(validator.getFirstError(), file, callback); } @@ -86,9 +86,7 @@ const storePicture = function(ctx, principalId, file, callback) { file.type = mime.getType(file.name); // Only images can be uploaded - if ( - !_.contains(['image/jpg', 'image/jpeg', 'image/gif', 'image/png', 'image/bmp'], file.type) - ) { + if (!_.contains(['image/jpg', 'image/jpeg', 'image/gif', 'image/png', 'image/bmp'], file.type)) { return callback({ code: 400, msg: 'Only images are accepted files' }); } @@ -130,19 +128,14 @@ const _storeLargePicture = function(ctx, principalId, file, callback) { // Store the oriented file const options = _getProfilePictureStorageOptions(principalId, Date.now(), 'large', '.jpg'); - ContentUtil.getStorageBackend(ctx).store( - ctx.tenant().alias, - convertedFile, - options, - (err, largePictureUri) => { - if (err) { - return _cleanupOnError(err, convertedFile, callback); - } - - // By this point the temp file has been removed from disk, no need to clean up in error cases below - return PrincipalsDAO.updatePrincipal(principalId, { largePictureUri }, callback); + ContentUtil.getStorageBackend(ctx).store(ctx.tenant().alias, convertedFile, options, (err, largePictureUri) => { + if (err) { + return _cleanupOnError(err, convertedFile, callback); } - ); + + // By this point the temp file has been removed from disk, no need to clean up in error cases below + return PrincipalsDAO.updatePrincipal(principalId, { largePictureUri }, callback); + }); }); }); }; @@ -175,9 +168,7 @@ const generateSizes = function(ctx, principalId, x, y, width, callback) { validator .check(null, { code: 401, msg: 'You have to be logged in to be able to update a picture' }) .isLoggedInUser(ctx); - validator - .check(principalId, { code: 400, msg: 'A principal id must be provided' }) - .isPrincipalId(); + validator.check(principalId, { code: 400, msg: 'A principal id must be provided' }).isPrincipalId(); validator.check(x, { code: 400, msg: 'The x value must be a positive integer' }).isInt(); validator.check(x, { code: 400, msg: 'The x value must be a positive integer' }).min(0); validator.check(y, { code: 400, msg: 'The y value must be a positive integer' }).isInt(); @@ -198,6 +189,7 @@ const generateSizes = function(ctx, principalId, x, y, width, callback) { if (err) { return callback(err); } + if (!principal.picture.largeUri) { return callback({ code: 400, msg: 'This principal has no large picture' }); } @@ -215,6 +207,7 @@ const generateSizes = function(ctx, principalId, x, y, width, callback) { // Return the full user profile return PrincipalsUtil.getPrincipal(ctx, principalId, callback); } + // Emit an event indicating that a group's picture has been set PrincipalsEmitter.emit(PrincipalsConstants.events.SET_GROUP_PICTURE, ctx, principal); @@ -271,6 +264,7 @@ const _generateSizes = function(ctx, principal, x, y, width, callback) { if (err) { return callback(err); } + if (removalError) { return callback(removalError); } @@ -299,11 +293,7 @@ const _storeCroppedPictures = function(ctx, principal, files, callback) { const now = Date.now(); // Get the the small image - let key = util.format( - '%sx%s', - PrincipalsConstants.picture.size.SMALL, - PrincipalsConstants.picture.size.SMALL - ); + let key = util.format('%sx%s', PrincipalsConstants.picture.size.SMALL, PrincipalsConstants.picture.size.SMALL); const smallImage = files[key]; // Store the image with a correct filename. We explicitly add a correct extension as nginx uses it @@ -320,11 +310,7 @@ const _storeCroppedPictures = function(ctx, principal, files, callback) { } // Get the medium image, determine the correct extension and store it - key = util.format( - '%sx%s', - PrincipalsConstants.picture.size.MEDIUM, - PrincipalsConstants.picture.size.MEDIUM - ); + key = util.format('%sx%s', PrincipalsConstants.picture.size.MEDIUM, PrincipalsConstants.picture.size.MEDIUM); const mediumImage = files[key]; options = _getProfilePictureStorageOptions( @@ -355,13 +341,7 @@ const _storeCroppedPictures = function(ctx, principal, files, callback) { * @param {Group|User} callback.principal The updated principal object * @api private */ -const _saveCroppedPictureUris = function( - ctx, - principal, - smallPictureUri, - mediumPictureUri, - callback -) { +const _saveCroppedPictureUris = function(ctx, principal, smallPictureUri, mediumPictureUri, callback) { // Apply the updates to the `principal` object const profileFields = { smallPictureUri, mediumPictureUri }; PrincipalsDAO.updatePrincipal(principal.id, profileFields, err => { @@ -377,19 +357,9 @@ const _saveCroppedPictureUris = function( // Fire the appropriate update event, depending if the principal is a user or a group if (PrincipalsUtil.isUser(principal.id)) { - PrincipalsEmitter.emit( - PrincipalsConstants.events.UPDATED_USER, - ctx, - newPrincipal, - principal - ); + PrincipalsEmitter.emit(PrincipalsConstants.events.UPDATED_USER, ctx, newPrincipal, principal); } else { - PrincipalsEmitter.emit( - PrincipalsConstants.events.UPDATED_GROUP, - ctx, - newPrincipal, - principal - ); + PrincipalsEmitter.emit(PrincipalsConstants.events.UPDATED_GROUP, ctx, newPrincipal, principal); } return callback(null, newPrincipal); @@ -477,7 +447,4 @@ const _cleanupOnError = function(error, file, callback) { } }; -module.exports = { - storePicture, - generateSizes -}; +export { storePicture, generateSizes }; diff --git a/packages/oae-principals/lib/api.termsAndConditions.js b/packages/oae-principals/lib/api.termsAndConditions.js index bfc1e63ffc..e8869594cc 100644 --- a/packages/oae-principals/lib/api.termsAndConditions.js +++ b/packages/oae-principals/lib/api.termsAndConditions.js @@ -14,13 +14,14 @@ */ /* eslint-disable unicorn/filename-case */ -const AuthzUtil = require('oae-authz/lib/util'); -const ConfigAPI = require('oae-config'); -const { Validator } = require('oae-util/lib/validator'); +import { Validator } from 'oae-util/lib/validator'; +import { setUpConfig } from 'oae-config'; -const PrincipalsConfig = ConfigAPI.setUpConfig('oae-principals'); -const PrincipalsDAO = require('./internal/dao'); -const PrincipalsUtil = require('./util'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as PrincipalsDAO from './internal/dao'; +import * as PrincipalsUtil from './util'; + +const PrincipalsConfig = setUpConfig('oae-principals'); /** * Get the Terms and Conditions text for a tenant. @@ -125,8 +126,4 @@ const needsToAcceptTermsAndConditions = function(ctx) { return ctx.user().acceptedTC < lastUpdated.getTime(); }; -module.exports = { - getTermsAndConditions, - acceptTermsAndConditions, - needsToAcceptTermsAndConditions -}; +export { getTermsAndConditions, acceptTermsAndConditions, needsToAcceptTermsAndConditions }; diff --git a/packages/oae-principals/lib/api.user.js b/packages/oae-principals/lib/api.user.js index a6d07645c3..8ac6cbd611 100644 --- a/packages/oae-principals/lib/api.user.js +++ b/packages/oae-principals/lib/api.user.js @@ -13,40 +13,43 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const util = require('util'); -const _ = require('underscore'); -const async = require('async'); -const clone = require('clone'); -const csv = require('csv'); -const dateFormat = require('dateformat'); -const jszip = require('jszip'); -const ShortId = require('shortid'); - -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzUtil = require('oae-authz/lib/util'); -const ConfigAPI = require('oae-config'); -const ContentAPI = require('oae-content'); -const ContentUtil = require('oae-content/lib/internal/util'); -const { Context } = require('oae-context'); -const DiscussionsAPI = require('oae-discussions'); -const EmailAPI = require('oae-email'); -const log = require('oae-logger').logger('oae-principals'); -const MeetingsAPI = require('oae-jitsi'); -const OaeUtil = require('oae-util/lib/util'); -const { Validator } = require('oae-util/lib/validator'); -const TenantsAPI = require('oae-tenants'); -const TenantsUtil = require('oae-tenants/lib/util'); -const Signature = require('oae-util/lib/signature'); - -const PrincipalsConfig = ConfigAPI.setUpConfig('oae-principals'); -const { PrincipalsConstants } = require('./constants'); -const PrincipalsDAO = require('./internal/dao'); -const PrincipalsEmitter = require('./internal/emitter'); -const PrincipalsTermsAndConditionsAPI = require('./api.termsAndConditions'); -const PrincipalsUtil = require('./util'); -const { User } = require('./model'); +import fs from 'fs'; +import util from 'util'; +import _ from 'underscore'; +import async from 'async'; +import clone from 'clone'; +import csv from 'csv'; +import dateFormat from 'dateformat'; +import jszip from 'jszip'; +import ShortId from 'shortid'; + +import { getTenantSkinVariables } from 'oae-ui'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ContentAPI from 'oae-content'; +import * as ContentUtil from 'oae-content/lib/internal/util'; +import * as DiscussionsAPI from 'oae-discussions'; +import * as EmailAPI from 'oae-email'; +import { logger } from 'oae-logger'; +import * as MeetingsAPI from 'oae-jitsi'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as TenantsAPI from 'oae-tenants'; +import * as TenantsUtil from 'oae-tenants/lib/util'; +import * as Signature from 'oae-util/lib/signature'; +import { setUpConfig } from 'oae-config'; +import { Context } from 'oae-context'; +import { Validator } from 'oae-util/lib/validator'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import * as PrincipalsDAO from './internal/dao'; +import PrincipalsEmitter from './internal/emitter'; +import * as PrincipalsTermsAndConditionsAPI from './api.termsAndConditions'; +import * as PrincipalsUtil from './util'; + +import { PrincipalsConstants } from './constants'; +import { User } from './model'; + +const log = logger('oae-principals'); +const PrincipalsConfig = setUpConfig('oae-principals'); const fullUserProfileDecorators = {}; @@ -1216,7 +1219,7 @@ const _sendEmailToken = function(ctx, user, email, token, callback) { tenant: ctx.tenant(), user: userToEmail, baseUrl: TenantsUtil.getBaseUrl(ctx.tenant()), - skinVariables: require('oae-ui').getTenantSkinVariables(ctx.tenant().alias), + skinVariables: getTenantSkinVariables(ctx.tenant().alias), token, verificationUrl }; @@ -2246,7 +2249,7 @@ const _getNewFileName = function(fileExt, fileName, folder) { } }; -module.exports = { +export { registerFullUserProfileDecorator, createUser, importUsers, diff --git a/packages/oae-principals/lib/constants.js b/packages/oae-principals/lib/constants.js index f9c67b542c..e57a863811 100644 --- a/packages/oae-principals/lib/constants.js +++ b/packages/oae-principals/lib/constants.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const { AuthzConstants } = require('oae-authz/lib/constants'); +import { AuthzConstants } from 'oae-authz/lib/constants'; const PrincipalsConstants = {}; @@ -89,4 +89,4 @@ PrincipalsConstants.requestStatus = { PENDING: 'pending' }; -module.exports = { PrincipalsConstants }; +export { PrincipalsConstants }; diff --git a/packages/oae-principals/lib/delete.js b/packages/oae-principals/lib/delete.js index 421140b400..ea83afb2f9 100644 --- a/packages/oae-principals/lib/delete.js +++ b/packages/oae-principals/lib/delete.js @@ -13,16 +13,18 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; +import Counter from 'oae-util/lib/counter'; -const AuthzAPI = require('oae-authz'); -const Counter = require('oae-util/lib/counter'); +import * as AuthzAPI from 'oae-authz'; -const groupDeleteLog = require('oae-logger').logger('group-delete'); -const groupRestoreLog = require('oae-logger').logger('group-restore'); -const PrincipalsEmitter = require('./internal/emitter'); -const { PrincipalsConstants } = require('./constants'); +import { logger } from 'oae-logger'; +import { PrincipalsConstants } from './constants'; +import PrincipalsEmitter from './internal/emitter'; + +const groupDeleteLog = logger('group-delete'); +const groupRestoreLog = logger('group-restore'); // Manage all handlers that have been registered for performing operations when a principal has been // deleted or restored in the system @@ -55,13 +57,9 @@ const deleteCounter = new Counter(); */ const registerGroupDeleteHandler = function(name, handler) { if (_groupDeleteHandlers[name]) { - throw new Error( - util.format('Attempted to register multiple group delete handlers for name "%s"', name) - ); + throw new Error(util.format('Attempted to register multiple group delete handlers for name "%s"', name)); } else if (!_.isFunction(handler)) { - throw new TypeError( - util.format('Attempted to register non-function group delete handler for name "%s"', name) - ); + throw new TypeError(util.format('Attempted to register non-function group delete handler for name "%s"', name)); } _groupDeleteHandlers[name] = handler; @@ -86,13 +84,9 @@ const registerGroupDeleteHandler = function(name, handler) { */ const registerGroupRestoreHandler = function(name, handler) { if (_groupRestoreHandlers[name]) { - throw new Error( - util.format('Attempted to register multiple group restore handlers for name "%s"', name) - ); + throw new Error(util.format('Attempted to register multiple group restore handlers for name "%s"', name)); } else if (!_.isFunction(handler)) { - throw new TypeError( - util.format('Attempted to register non-function group restore handler for name "%s"', name) - ); + throw new TypeError(util.format('Attempted to register non-function group restore handler for name "%s"', name)); } _groupRestoreHandlers[name] = handler; @@ -178,8 +172,8 @@ const _invokeGroupHandlers = function(log, handlers, group) { * @api private */ const _invokeHandlers = function(...args) { - let [log, handlers, principal] = args; - // const args = Array.prototype.slice.call(arguments); + const [log, handlers, principal] = args; + // Const args = Array.prototype.slice.call(arguments); // The arguments for the handler (including the `principal`) start from the 2nd argument and // continue until the end of the arguments list const handlerArgs = args.slice(2); @@ -208,10 +202,7 @@ const _invokeHandlers = function(...args) { ); } - return log().debug( - { principalId: principal.id, handlerName: name }, - 'Successfully processed handler' - ); + return log().debug({ principalId: principal.id, handlerName: name }, 'Successfully processed handler'); }); // Invoke the handler with our arguments array @@ -241,7 +232,7 @@ PrincipalsEmitter.on(PrincipalsConstants.events.RESTORED_GROUP, (ctx, group) => invokeGroupRestoreHandlers(group); }); -module.exports = { +export { registerGroupDeleteHandler, registerGroupRestoreHandler, invokeGroupDeleteHandlers, diff --git a/packages/oae-principals/lib/init.js b/packages/oae-principals/lib/init.js index 04aca084f6..122335f2fc 100644 --- a/packages/oae-principals/lib/init.js +++ b/packages/oae-principals/lib/init.js @@ -13,15 +13,16 @@ * permissions and limitations under the License. */ -const util = require('util'); +import util from 'util'; -const AuthenticationAPI = require('oae-authentication'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const { Context } = require('oae-context'); -const TenantsAPI = require('oae-tenants'); -const { User } = require('oae-principals/lib/model'); +import * as AuthenticationAPI from 'oae-authentication'; +import * as TenantsAPI from 'oae-tenants'; -module.exports = function(config, callback) { +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { Context } from 'oae-context'; +import { User } from 'oae-principals/lib/model'; + +export function init(config, callback) { // Initialize activity capabilities require('oae-principals/lib/activity'); // eslint-disable-line import/no-unassigned-import @@ -38,7 +39,7 @@ module.exports = function(config, callback) { require('oae-principals/lib/delete'); // eslint-disable-line import/no-unassigned-import return _ensureGlobalAdmin(config, callback); -}; +} /** * Ensure that the default global administrative user exists with username "administrator", and create diff --git a/packages/oae-principals/lib/internal/dao.js b/packages/oae-principals/lib/internal/dao.js index 54dc8b4d45..4893798d6a 100644 --- a/packages/oae-principals/lib/internal/dao.js +++ b/packages/oae-principals/lib/internal/dao.js @@ -13,21 +13,25 @@ * permissions and limitations under the License. */ -const util = require('util'); -const { sanitize } = require('validator'); -const AuthzDelete = require('oae-authz/lib/delete'); -const AuthzUtil = require('oae-authz/lib/util'); -const Cassandra = require('oae-util/lib/cassandra'); -const log = require('oae-logger').logger('principals-dao'); -const OaeUtil = require('oae-util/lib/util'); -const Redis = require('oae-util/lib/redis'); -const { Validator } = require('oae-authz/lib/validator'); -const _ = require('underscore'); - -const { Group } = require('oae-principals/lib/model'); -const PrincipalsConfig = require('oae-config').setUpConfig('oae-principals'); -const { User } = require('oae-principals/lib/model'); -const { PrincipalsConstants } = require('../constants'); +import util from 'util'; +import _ from 'underscore'; +import { logger } from 'oae-logger'; +import { setUpConfig } from 'oae-config'; + +import * as AuthzDelete from 'oae-authz/lib/delete'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as Redis from 'oae-util/lib/redis'; + +import { sanitize } from 'validator'; +import { Group } from 'oae-principals/lib/model'; +import { Validator } from 'oae-authz/lib/validator'; +import { User } from 'oae-principals/lib/model'; +import { PrincipalsConstants } from '../constants'; + +const log = logger('principals-dao'); +const PrincipalsConfig = setUpConfig('oae-principals'); const RESTRICTED_FIELDS = ['acceptedTC', 'admin:tenant', 'admin:global', 'deleted']; @@ -1147,7 +1151,7 @@ const getJoinGroupRequests = function(groupId, callback) { }); }; -module.exports = { +export { createUser, createGroup, getPrincipal, diff --git a/packages/oae-principals/lib/internal/emitter.js b/packages/oae-principals/lib/internal/emitter.js index c8b9cf1cb9..d39d753664 100644 --- a/packages/oae-principals/lib/internal/emitter.js +++ b/packages/oae-principals/lib/internal/emitter.js @@ -13,6 +13,6 @@ * permissions and limitations under the License. */ -var EmitterAPI = require('oae-emitter'); +import * as EmitterAPI from 'oae-emitter'; -module.exports = new EmitterAPI.EventEmitter(); +export default new EmitterAPI.EventEmitter(); diff --git a/packages/oae-principals/lib/invitations.js b/packages/oae-principals/lib/invitations.js index aaa20cf28d..34048a4526 100644 --- a/packages/oae-principals/lib/invitations.js +++ b/packages/oae-principals/lib/invitations.js @@ -13,20 +13,22 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const { Context } = require('oae-context'); -const { Invitation } = require('oae-authz/lib/invitations/model'); -const ResourceActions = require('oae-resource/lib/actions'); -const { ResourceConstants } = require('oae-resource/lib/constants'); +import { Invitation } from 'oae-authz/lib/invitations/model'; +import { ResourceConstants } from 'oae-resource/lib/constants'; +import { Context } from 'oae-context'; +import { PrincipalsConstants } from 'oae-principals/lib/constants'; -const { PrincipalsConstants } = require('oae-principals/lib/constants'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const PrincipalsEmitter = require('oae-principals/lib/internal/emitter'); -const PrincipalsUtil = require('oae-principals/lib/util'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ResourceActions from 'oae-resource/lib/actions'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import PrincipalsEmitter from 'oae-principals/lib/internal/emitter'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; -const log = require('oae-logger').logger('oae-principals-invitations'); +import { logger } from 'oae-logger'; + +const log = logger('oae-principals-invitations'); /*! * When an invitation is accepted, pass on the events to update group members and then feed back the diff --git a/packages/oae-principals/lib/libraries/members.js b/packages/oae-principals/lib/libraries/members.js index 8fb65c126a..028aaaa06f 100644 --- a/packages/oae-principals/lib/libraries/members.js +++ b/packages/oae-principals/lib/libraries/members.js @@ -13,20 +13,23 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import PrincipalsEmitter from 'oae-principals/lib/internal/emitter'; -const AuthzAPI = require('oae-authz'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzUtil = require('oae-authz/lib/util'); -const LibraryAPI = require('oae-library'); +import _ from 'underscore'; -const PrincipalsEmitter = require('oae-principals/lib/internal/emitter'); -const { PrincipalsConstants } = require('oae-principals/lib/constants'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const PrincipalsDelete = require('oae-principals/lib/delete'); +import * as AuthzAPI from 'oae-authz'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as LibraryAPI from 'oae-library'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as PrincipalsDelete from 'oae-principals/lib/delete'; -const log = require('oae-logger').logger('principals-memberslibrary'); +import { PrincipalsConstants } from 'oae-principals/lib/constants'; +import { AuthzConstants } from 'oae-authz/lib/constants'; + +import { logger } from 'oae-logger'; + +const log = logger('principals-memberslibrary'); /// ////// // API // @@ -184,10 +187,7 @@ PrincipalsEmitter.when( // performance issues with many group members updates) LibraryAPI.Index.purge(PrincipalsConstants.library.MEMBERS_INDEX_NAME, group.id, err => { if (err) { - log().error( - { err, groupId: group.id }, - 'An unexpected error occurred trying to purge a group members library' - ); + log().error({ err, groupId: group.id }, 'An unexpected error occurred trying to purge a group members library'); return callback(err); } @@ -199,30 +199,27 @@ PrincipalsEmitter.when( /*! * When someone joins a group, we should add them to the group's members library */ -PrincipalsEmitter.when( - PrincipalsConstants.events.JOINED_GROUP, - (ctx, group, oldGroup, memberChangeInfo, callback) => { - const joinRole = memberChangeInfo.changes[ctx.user().id]; - const entry = { - id: group.id, - rank: _getMembersLibraryRank(group.id, ctx.user(), joinRole), - resource: ctx.user(), - value: joinRole - }; - - LibraryAPI.Index.insert(PrincipalsConstants.library.MEMBERS_INDEX_NAME, [entry], err => { - if (err) { - log().error( - { err, groupId: group.id, userId: ctx.user().id }, - 'An unexpected error occurred trying to insert a user into a group members library' - ); - return callback(err); - } +PrincipalsEmitter.when(PrincipalsConstants.events.JOINED_GROUP, (ctx, group, oldGroup, memberChangeInfo, callback) => { + const joinRole = memberChangeInfo.changes[ctx.user().id]; + const entry = { + id: group.id, + rank: _getMembersLibraryRank(group.id, ctx.user(), joinRole), + resource: ctx.user(), + value: joinRole + }; - return callback(); - }); - } -); + LibraryAPI.Index.insert(PrincipalsConstants.library.MEMBERS_INDEX_NAME, [entry], err => { + if (err) { + log().error( + { err, groupId: group.id, userId: ctx.user().id }, + 'An unexpected error occurred trying to insert a user into a group members library' + ); + return callback(err); + } + + return callback(); + }); +}); /*! * When someone leaves a group, we should remove them from the group's members library. We don't @@ -309,6 +306,7 @@ const _hasProfilePicture = function(principal) { if (principal.picture && principal.picture.smallUri) { return true; } + return false; }; @@ -337,6 +335,4 @@ const _handleInvalidateMembersLibrary = function(group, membershipsGraph, member PrincipalsDelete.registerGroupDeleteHandler('members-library', _handleInvalidateMembersLibrary); PrincipalsDelete.registerGroupRestoreHandler('members-library', _handleInvalidateMembersLibrary); -module.exports = { - list -}; +export { list }; diff --git a/packages/oae-principals/lib/libraries/memberships.js b/packages/oae-principals/lib/libraries/memberships.js index dc6750ed97..84e25d520c 100644 --- a/packages/oae-principals/lib/libraries/memberships.js +++ b/packages/oae-principals/lib/libraries/memberships.js @@ -13,23 +13,26 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import PrincipalsEmitter from 'oae-principals/lib/internal/emitter'; -const AuthzAPI = require('oae-authz'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzDelete = require('oae-authz/lib/delete'); -const AuthzUtil = require('oae-authz/lib/util'); -const LibraryAPI = require('oae-library'); -const { SearchConstants } = require('oae-search/lib/constants'); -const SearchUtil = require('oae-search/lib/util'); +import _ from 'underscore'; -const { PrincipalsConstants } = require('oae-principals/lib/constants'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const PrincipalsDelete = require('oae-principals/lib/delete'); -const PrincipalsEmitter = require('oae-principals/lib/internal/emitter'); -const PrincipalsUtil = require('oae-principals/lib/util'); +import * as AuthzAPI from 'oae-authz'; +import * as AuthzDelete from 'oae-authz/lib/delete'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as LibraryAPI from 'oae-library'; +import * as SearchUtil from 'oae-search/lib/util'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as PrincipalsDelete from 'oae-principals/lib/delete'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; -const log = require('oae-logger').logger('principals-memberships'); +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { SearchConstants } from 'oae-search/lib/constants'; +import { PrincipalsConstants } from 'oae-principals/lib/constants'; + +import { logger } from 'oae-logger'; + +const log = logger('principals-memberships'); /// ///////////////////////////////////////// // LIBRARY INDEX AND SEARCH REGISTRATIONS // @@ -369,6 +372,7 @@ const _touchMembershipLibraries = function(group, oldLastModified, memberChangeI if (oldLastModified) { return _updateMembershipsLibraries(changedGroup, explodedInsertedPrincipals, callback); } + return callback(); }); }); diff --git a/packages/oae-principals/lib/migration.js b/packages/oae-principals/lib/migration.js index 4520195a7b..f2f3efbe99 100644 --- a/packages/oae-principals/lib/migration.js +++ b/packages/oae-principals/lib/migration.js @@ -1,4 +1,4 @@ -const Cassandra = require('oae-util/lib/cassandra'); +import { runQuery, createColumnFamilies } from 'oae-util/lib/cassandra'; /** * Ensure that the all of the principal-related schemas are created. If they already exist, this method will not @@ -10,7 +10,7 @@ const Cassandra = require('oae-util/lib/cassandra'); */ const ensureSchema = function(callback) { // Both user and group information will be stored inside of the Principals CF - Cassandra.createColumnFamilies( + createColumnFamilies( { Principals: 'CREATE TABLE "Principals" ("principalId" text PRIMARY KEY, "tenantAlias" text, "displayName" text, "description" text, "email" text, "emailPreference" text, "visibility" text, "joinable" text, "lastModified" text, "locale" text, "publicAlias" text, "largePictureUri" text, "mediumPictureUri" text, "smallPictureUri" text, "admin:global" text, "admin:tenant" text, "notificationsUnread" text, "notificationsLastRead" text, "acceptedTC" text, "createdBy" text, "created" timestamp, "deleted" timestamp)', @@ -32,11 +32,11 @@ const ensureSchema = function(callback) { 'CREATE TABLE "GroupJoinRequestsByGroup" ("groupId" text, "principalId" text, "created_at" text, "updated_at" text, "status" text, PRIMARY KEY ("groupId", "principalId"))' }, () => { - Cassandra.runQuery('CREATE INDEX IF NOT EXISTS ON "Principals" ("tenantAlias")', [], () => { + runQuery('CREATE INDEX IF NOT EXISTS ON "Principals" ("tenantAlias")', [], () => { return callback(); }); } ); }; -module.exports = { ensureSchema }; +export { ensureSchema }; diff --git a/packages/oae-principals/lib/model.group.js b/packages/oae-principals/lib/model.group.js index 4e7a8c7e52..59d6f96bb6 100644 --- a/packages/oae-principals/lib/model.group.js +++ b/packages/oae-principals/lib/model.group.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const TenantsAPI = require('oae-tenants'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as TenantsAPI from 'oae-tenants'; /** * The Group model. @@ -36,7 +36,7 @@ const TenantsAPI = require('oae-tenants'); * @param {String} [opts.mediumPictureUri] The uri of the medium picture. It will be made available at user.picture.mediumUri * @param {String} [opts.largePictureUri] The uri of the large picture. It will be made available at user.picture.largeUri */ -module.exports.Group = function(tenantAlias, id, displayName, opts) { +export const Group = function(tenantAlias, id, displayName, opts) { opts = opts || {}; const { resourceId } = AuthzUtil.getResourceFromId(id); diff --git a/packages/oae-principals/lib/model.js b/packages/oae-principals/lib/model.js index 29ce335903..c3d95a40e5 100644 --- a/packages/oae-principals/lib/model.js +++ b/packages/oae-principals/lib/model.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -var _ = require('underscore'); -var userModel = require('oae-principals/lib/model.user.js'); -var groupModel = require('oae-principals/lib/model.group.js'); +import _ from 'underscore'; +import { User } from 'oae-principals/lib/model.user.js'; +import { Group } from 'oae-principals/lib/model.group.js'; // Expose the user and group model. -_.extend(module.exports, userModel, groupModel); +export { User, Group }; diff --git a/packages/oae-principals/lib/model.user.js b/packages/oae-principals/lib/model.user.js index 5ecd0d712e..fb258af048 100644 --- a/packages/oae-principals/lib/model.user.js +++ b/packages/oae-principals/lib/model.user.js @@ -13,11 +13,11 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; -const AuthzUtil = require('oae-authz/lib/util'); -const TenantsAPI = require('oae-tenants'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as TenantsAPI from 'oae-tenants'; /** * The User model. @@ -42,7 +42,7 @@ const TenantsAPI = require('oae-tenants'); * @param {Boolean} [opts.isGlobalAdmin] Whether or not the user is a global admin * @param {Boolean} [opts.isTenantAdmin] Whether or not the user is a tenant admin */ -module.exports.User = function(tenantAlias, id, displayName, email, opts) { +export const User = function(tenantAlias, id, displayName, email, opts) { opts = opts || {}; // Explicit checks on true for admin. diff --git a/packages/oae-principals/lib/rest.group.js b/packages/oae-principals/lib/rest.group.js index 54d66773be..8924c4389f 100644 --- a/packages/oae-principals/lib/rest.group.js +++ b/packages/oae-principals/lib/rest.group.js @@ -13,13 +13,13 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const { AuthzConstants } = require('oae-authz/lib/constants'); -const OAE = require('oae-util/lib/oae'); -const OaeUtil = require('oae-util/lib/util'); +import { AuthzConstants } from 'oae-authz/lib/constants'; -const PrincipalsAPI = require('./api'); +import * as OAE from 'oae-util/lib/oae'; +import * as OaeUtil from 'oae-util/lib/util'; +import PrincipalsAPI from './api'; /** * @REST postGroupCreate @@ -175,6 +175,7 @@ OAE.tenantRouter.on('post', '/api/group/:groupId', (req, res) => { if (err) { return res.status(err.code).send(err.msg); } + res.status(200).send(updatedGroup); }); }); @@ -202,6 +203,7 @@ OAE.tenantRouter.on('get', '/api/group/:groupId/members', (req, res) => { if (err) { return res.status(err.code).send(err.msg); } + res.status(200).send({ results: members, nextToken }); }); }); @@ -244,6 +246,7 @@ OAE.tenantRouter.on('post', '/api/group/:groupId/members', (req, res) => { if (err) { return res.status(err.code).send(err.msg); } + res.status(200).end(); }); }); @@ -404,6 +407,7 @@ OAE.tenantRouter.on('post', '/api/group/:groupId/join-request', (req, res) => { if (err) { return res.status(err.code).send(err.msg); } + return res.status(200).end(); }); }); @@ -427,6 +431,7 @@ OAE.tenantRouter.on('get', '/api/group/:groupId/join-request/mine', (req, res) = if (err) { return res.status(err.code).send(err.msg); } + return res.status(200).send(request); }); }); @@ -459,6 +464,7 @@ OAE.tenantRouter.on('get', '/api/group/:groupId/join-request/all', (req, res) => if (err) { return res.status(err.code).send(err.msg); } + return res.status(200).send({ results: requests, nextToken }); } ); @@ -493,6 +499,7 @@ OAE.tenantRouter.on('put', '/api/group/:groupId/join-request', (req, res) => { if (err) { return res.status(err.code).send(err.msg); } + return res.status(200).end(); } ); diff --git a/packages/oae-principals/lib/rest.js b/packages/oae-principals/lib/rest.js index fd166691e5..1f73de8fef 100644 --- a/packages/oae-principals/lib/rest.js +++ b/packages/oae-principals/lib/rest.js @@ -13,13 +13,23 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const locale = require('locale'); +import _ from 'underscore'; +import locale from 'locale'; -const OAE = require('oae-util/lib/oae'); +import * as OAE from 'oae-util/lib/oae'; -let languages = require('../config/user').user.elements.defaultLanguage.list; -const PrincipalsAPI = require('./api'); +// eslint-disable-next-line no-unused-vars +import * as UserRESTEndpoints from 'oae-principals/lib/rest.user.js'; +// eslint-disable-next-line no-unused-vars +import * as GroupRESTEndpoints from 'oae-principals/lib/rest.group.js'; +import * as userConfig from '../config/user'; +import PrincipalsAPI from './api'; + +// Require the user related REST endpoints + +// Require the group related REST endpoints + +let languages = userConfig.user.elements.defaultLanguage.list; /// ///////// // LOCALE // @@ -63,9 +73,7 @@ OAE.tenantServer.use((req, res, next) => { PrincipalsAPI.needsToAcceptTermsAndConditions(ctx) && !_isWhiteListed(req.path) ) { - return res - .status(419) - .send('You need to accept the Terms and Conditions before you can interact with this tenant'); + return res.status(419).send('You need to accept the Terms and Conditions before you can interact with this tenant'); } return next(); @@ -108,24 +116,11 @@ const _isWhiteListed = function(url) { * @HttpResponse 401 You have to be logged in to be able to update a picture */ OAE.tenantRouter.on('post', '/api/crop', (req, res) => { - PrincipalsAPI.generateSizes( - req.ctx, - req.body.principalId, - req.body.x, - req.body.y, - req.body.width, - (err, data) => { - if (err) { - return res.status(err.code).send(err.msg); - } - - return res.status(200).send(data); + PrincipalsAPI.generateSizes(req.ctx, req.body.principalId, req.body.x, req.body.y, req.body.width, (err, data) => { + if (err) { + return res.status(err.code).send(err.msg); } - ); -}); - -// Require the user related REST endpoints -require('oae-principals/lib/rest.user.js'); // eslint-disable-line import/no-unassigned-import -// Require the group related REST endpoints -require('oae-principals/lib/rest.group.js'); // eslint-disable-line import/no-unassigned-import + return res.status(200).send(data); + }); +}); diff --git a/packages/oae-principals/lib/rest.user.js b/packages/oae-principals/lib/rest.user.js index c2caff45a8..6e0541df96 100644 --- a/packages/oae-principals/lib/rest.user.js +++ b/packages/oae-principals/lib/rest.user.js @@ -13,17 +13,19 @@ * permissions and limitations under the License. */ -const util = require('util'); -const { Recaptcha } = require('recaptcha'); +import util from 'util'; +import { setUpConfig } from 'oae-config'; -const AuthenticationAPI = require('oae-authentication'); -const { AuthenticationConstants } = require('oae-authentication/lib/constants'); -const { LoginId } = require('oae-authentication/lib/model'); -const OAE = require('oae-util/lib/oae'); -const OaeUtil = require('oae-util/lib/util'); +import * as AuthenticationAPI from 'oae-authentication'; +import * as OAE from 'oae-util/lib/oae'; +import * as OaeUtil from 'oae-util/lib/util'; -const PrincipalsConfig = require('oae-config').setUpConfig('oae-principals'); -const PrincipalsAPI = require('./api'); +import { Recaptcha } from 'recaptcha'; +import { AuthenticationConstants } from 'oae-authentication/lib/constants'; +import { LoginId } from 'oae-authentication/lib/model'; +import PrincipalsAPI from './api'; + +const PrincipalsConfig = setUpConfig('oae-principals'); /** * @REST getUserTermsAndConditions @@ -107,10 +109,9 @@ OAE.globalAdminRouter.on('post', '/api/user/createGlobalAdminUser', (req, res) = if (err) { return res.status(err.code).send(err.msg); } + if (!created) { - return res - .status(400) - .send(util.format('A user with username "%s" already exists', req.body.username)); + return res.status(400).send(util.format('A user with username "%s" already exists', req.body.username)); } return res.status(201).send(user); @@ -174,12 +175,9 @@ OAE.globalAdminRouter.on('post', '/api/user/createGlobalAdminUser', (req, res) = const _handleCreateTenantAdminUser = function(req, res) { const { ctx } = req; const tenantAlias = req.params.tenantAlias || ctx.tenant().alias; - const loginId = new LoginId( - tenantAlias, - AuthenticationConstants.providers.LOCAL, - req.body.username, - { password: req.body.password } - ); + const loginId = new LoginId(tenantAlias, AuthenticationConstants.providers.LOCAL, req.body.username, { + password: req.body.password + }); const opts = _getOptionalProfileParameters(req.body); // Create the user as a tenant administrator @@ -192,11 +190,7 @@ const _handleCreateTenantAdminUser = function(req, res) { }); }; -OAE.globalAdminRouter.on( - 'post', - '/api/user/:tenantAlias/createTenantAdminUser', - _handleCreateTenantAdminUser -); +OAE.globalAdminRouter.on('post', '/api/user/:tenantAlias/createTenantAdminUser', _handleCreateTenantAdminUser); OAE.tenantRouter.on('post', '/api/user/createTenantAdminUser', _handleCreateTenantAdminUser); /** @@ -228,12 +222,9 @@ OAE.tenantRouter.on('post', '/api/user/createTenantAdminUser', _handleCreateTena * @HttpResponse 404 A non-existing tenant was specified as the target for this user */ OAE.globalAdminRouter.on('post', '/api/user/:tenantAlias/create', (req, res) => { - const loginId = new LoginId( - req.params.tenantAlias, - AuthenticationConstants.providers.LOCAL, - req.body.username, - { password: req.body.password } - ); + const loginId = new LoginId(req.params.tenantAlias, AuthenticationConstants.providers.LOCAL, req.body.username, { + password: req.body.password + }); const opts = { visibility: req.body.visibility, email: req.body.email, @@ -295,8 +286,8 @@ OAE.tenantRouter.on('post', '/api/user/create', (req, res) => { opts.invitationToken = req.body.invitationToken; /*! - * Create a local user account - */ + * Create a local user account + */ const createUser = function() { AuthenticationAPI.getOrCreateUser( ctx, @@ -309,6 +300,7 @@ OAE.tenantRouter.on('post', '/api/user/create', (req, res) => { if (err) { return res.status(err.code).send(err.msg); } + if (!created) { return res .status(400) @@ -326,9 +318,11 @@ OAE.tenantRouter.on('post', '/api/user/create', (req, res) => { // If the current user is an admin, the reCaptcha verification can be skipped return createUser(); } + // Non-admin users cannot create accounts return res.status(401).end(); } + if (opts.invitationToken) { // Bypass recaptcha if an invitation token is provided. The process of creating a user will // fail if the invitation token is not valid @@ -336,11 +330,7 @@ OAE.tenantRouter.on('post', '/api/user/create', (req, res) => { } // Check if the Terms and Conditions has been agreed to (if applicable) - const needsTermsAndConditionsAgreement = PrincipalsConfig.getValue( - tenant.alias, - 'termsAndConditions', - 'enabled' - ); + const needsTermsAndConditionsAgreement = PrincipalsConfig.getValue(tenant.alias, 'termsAndConditions', 'enabled'); if (needsTermsAndConditionsAgreement && opts.acceptedTC !== true) { return res.status(400).send('You need to accept the Terms and Conditions'); } @@ -365,6 +355,7 @@ OAE.tenantRouter.on('post', '/api/user/create', (req, res) => { if (success) { return createUser(); } + return res.status(400).send('Invalid reCaptcha token'); }); }); @@ -750,6 +741,7 @@ const _getEmailToken = function(req, res) { if (err && err.code !== 404) { return res.status(err.code).send(err.msg); } + if (err) { email = null; } @@ -882,10 +874,8 @@ OAE.tenantRouter.on('get', '/api/user/:userId/export/:exportType', (req, res) => res.setHeader('Content-Type', 'application/zip'); res.writeHead(200); - zipFile - .generateAsync({ type: 'nodebuffer', platform: process.platform, streamFiles: true }) - .then(nodebuffer => { - res.end(nodebuffer); - }); + zipFile.generateAsync({ type: 'nodebuffer', platform: process.platform, streamFiles: true }).then(nodebuffer => { + res.end(nodebuffer); + }); }); }); diff --git a/packages/oae-principals/lib/restmodel.js b/packages/oae-principals/lib/restmodel.js index d6b20272d3..f47d45b2af 100644 --- a/packages/oae-principals/lib/restmodel.js +++ b/packages/oae-principals/lib/restmodel.js @@ -13,7 +13,6 @@ * permissions and limitations under the License. */ - /** * @RESTModel BasicUser * @@ -175,4 +174,3 @@ * @Property {BasicTenant} tenant The tenant to which this user is associated * @Property {string} visibility The visibility of the user [loggedin,private,public] */ - diff --git a/packages/oae-principals/lib/search.js b/packages/oae-principals/lib/search.js index f7f8b6862b..da0a0e0773 100644 --- a/packages/oae-principals/lib/search.js +++ b/packages/oae-principals/lib/search.js @@ -14,24 +14,26 @@ */ /* eslint-disable camelcase */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; +import { logger } from 'oae-logger'; -const AuthzSearch = require('oae-authz/lib/search'); -const AuthzUtil = require('oae-authz/lib/util'); -const ContentUtil = require('oae-content/lib/internal/util'); -const log = require('oae-logger').logger('principals-search'); -const OaeUtil = require('oae-util/lib/util'); -const SearchAPI = require('oae-search'); -const TenantsAPI = require('oae-tenants'); +import * as AuthzSearch from 'oae-authz/lib/search'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ContentUtil from 'oae-content/lib/internal/util'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as SearchAPI from 'oae-search'; +import * as TenantsAPI from 'oae-tenants'; -const PrincipalsAPI = require('oae-principals'); -const { PrincipalsConstants } = require('oae-principals/lib/constants'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const PrincipalsDelete = require('oae-principals/lib/delete'); -const PrincipalsUtil = require('oae-principals/lib/util'); +import { emitter } from 'oae-principals'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as PrincipalsDelete from 'oae-principals/lib/delete'; +import * as PrincipalsUtil from 'oae-principals/lib/util'; -const { User } = require('oae-principals/lib/model'); +import { PrincipalsConstants } from 'oae-principals/lib/constants'; +import { User } from 'oae-principals/lib/model'; + +const log = logger('principals-search'); /// ///////////////// // INDEXING TASKS // @@ -40,7 +42,7 @@ const { User } = require('oae-principals/lib/model'); /*! * When a user is created, we must index the user resource document */ -PrincipalsAPI.emitter.on(PrincipalsConstants.events.CREATED_USER, (ctx, user) => { +emitter.on(PrincipalsConstants.events.CREATED_USER, (ctx, user) => { SearchAPI.postIndexTask('user', [{ id: user.id }], { resource: true }); @@ -49,7 +51,7 @@ PrincipalsAPI.emitter.on(PrincipalsConstants.events.CREATED_USER, (ctx, user) => /*! * When a user is updated, we must reindex the user resource document */ -PrincipalsAPI.emitter.on(PrincipalsConstants.events.UPDATED_USER, (ctx, user) => { +emitter.on(PrincipalsConstants.events.UPDATED_USER, (ctx, user) => { SearchAPI.postIndexTask('user', [{ id: user.id }], { resource: true }); @@ -58,7 +60,7 @@ PrincipalsAPI.emitter.on(PrincipalsConstants.events.UPDATED_USER, (ctx, user) => /*! * When a user is deleted, we must reindex the user resource document */ -PrincipalsAPI.emitter.on(PrincipalsConstants.events.DELETED_USER, (ctx, user) => { +emitter.on(PrincipalsConstants.events.DELETED_USER, (ctx, user) => { SearchAPI.postIndexTask('user', [{ id: user.id }], { resource: true }); @@ -67,7 +69,7 @@ PrincipalsAPI.emitter.on(PrincipalsConstants.events.DELETED_USER, (ctx, user) => /*! * When a user is restored, we must reindex the user resource document */ -PrincipalsAPI.emitter.on(PrincipalsConstants.events.RESTORED_USER, (ctx, user) => { +emitter.on(PrincipalsConstants.events.RESTORED_USER, (ctx, user) => { SearchAPI.postIndexTask('user', [{ id: user.id }], { resource: true }); @@ -76,7 +78,7 @@ PrincipalsAPI.emitter.on(PrincipalsConstants.events.RESTORED_USER, (ctx, user) = /*! * When a user's email is verified, we must reindex the user resource document */ -PrincipalsAPI.emitter.on(PrincipalsConstants.events.VERIFIED_EMAIL, (ctx, user) => { +emitter.on(PrincipalsConstants.events.VERIFIED_EMAIL, (ctx, user) => { SearchAPI.postIndexTask('user', [{ id: user.id }], { resource: true }); @@ -85,7 +87,7 @@ PrincipalsAPI.emitter.on(PrincipalsConstants.events.VERIFIED_EMAIL, (ctx, user) /*! * When a group is created, we must index the group resource document and its members child document */ -PrincipalsAPI.emitter.on(PrincipalsConstants.events.CREATED_GROUP, (ctx, group, memberChangeInfo) => { +emitter.on(PrincipalsConstants.events.CREATED_GROUP, (ctx, group, memberChangeInfo) => { SearchAPI.postIndexTask('group', [{ id: group.id }], { resource: true, children: { @@ -100,7 +102,7 @@ PrincipalsAPI.emitter.on(PrincipalsConstants.events.CREATED_GROUP, (ctx, group, /*! * When a group is updated, we must reindex the user resource document */ -PrincipalsAPI.emitter.on(PrincipalsConstants.events.UPDATED_GROUP, (ctx, group) => { +emitter.on(PrincipalsConstants.events.UPDATED_GROUP, (ctx, group) => { SearchAPI.postIndexTask('group', [{ id: group.id }], { resource: true }); @@ -110,7 +112,7 @@ PrincipalsAPI.emitter.on(PrincipalsConstants.events.UPDATED_GROUP, (ctx, group) * When group members have been updated, we must both the group's members child document and all the * principals' child memberships documents */ -PrincipalsAPI.emitter.on( +emitter.on( PrincipalsConstants.events.UPDATED_GROUP_MEMBERS, // eslint-disable-next-line no-unused-vars (ctx, group, oldGroup, memberChangeInfo, opts) => { @@ -122,7 +124,7 @@ PrincipalsAPI.emitter.on( * When someone joins a group, we must both the group's members child document and the user's child * memberships documents */ -PrincipalsAPI.emitter.on(PrincipalsConstants.events.JOINED_GROUP, (ctx, group, oldGroup, memberChangeInfo) => { +emitter.on(PrincipalsConstants.events.JOINED_GROUP, (ctx, group, oldGroup, memberChangeInfo) => { _handleUpdateGroupMembers(ctx, group, _.keys(memberChangeInfo.changes)); }); @@ -130,7 +132,7 @@ PrincipalsAPI.emitter.on(PrincipalsConstants.events.JOINED_GROUP, (ctx, group, o * When someone leaves a group, we must both the group's members child document and the user's child * memberships documents */ -PrincipalsAPI.emitter.on(PrincipalsConstants.events.LEFT_GROUP, (ctx, group, memberChangeInfo) => { +emitter.on(PrincipalsConstants.events.LEFT_GROUP, (ctx, group, memberChangeInfo) => { _handleUpdateGroupMembers(ctx, group, _.keys(memberChangeInfo.changes)); }); diff --git a/packages/oae-principals/lib/test/util.js b/packages/oae-principals/lib/test/util.js index 5f3ee9a03a..5632cbeb83 100644 --- a/packages/oae-principals/lib/test/util.js +++ b/packages/oae-principals/lib/test/util.js @@ -14,26 +14,27 @@ */ /* eslint-disable no-unused-vars */ -const assert = require('assert'); -const fs = require('fs'); -const util = require('util'); -const path = require('path'); -const _ = require('underscore'); +import assert from 'assert'; +import fs from 'fs'; +import util from 'util'; +import path from 'path'; +import _ from 'underscore'; -const AuthzAPI = require('oae-authz'); +import * as AuthzAPI from 'oae-authz'; -const AuthzUtil = require('oae-authz/lib/util'); -const ConfigTestsUtil = require('oae-config/lib/test/util'); -const EmailAPI = require('oae-email'); -const LibraryAPI = require('oae-library'); -const OaeUtil = require('oae-util/lib/util'); -const PrincipalsDAO = require('oae-principals/lib/internal/dao'); -const RestAPI = require('oae-rest'); -const SearchTestUtil = require('oae-search/lib/test/util'); -const TestsUtil = require('oae-tests/lib/util'); +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ConfigTestsUtil from 'oae-config/lib/test/util'; +import * as EmailAPI from 'oae-email'; +import * as LibraryAPI from 'oae-library'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; +import * as RestAPI from 'oae-rest'; +import * as SearchTestUtil from 'oae-search/lib/test/util'; +import * as TestsUtil from 'oae-tests/lib/util'; +import * as AuthzTestUtil from 'oae-authz/lib/test/util'; -const PrincipalsAPI = require('oae-principals'); -const PrincipalsDelete = require('oae-principals/lib/delete'); +import { emitter } from 'oae-principals'; +import * as PrincipalsDelete from 'oae-principals/lib/delete'; /** * Import a batch of users from a CSV file. This function is a test utility function that wraps the REST API call and listens @@ -48,7 +49,7 @@ const importUsers = function(restCtx, tenantAlias, csvGenerator, authenticationS return callback(err); } - PrincipalsAPI.emitter.once('postCSVUserImport', callback); + emitter.once('postCSVUserImport', callback); }); }; @@ -484,7 +485,6 @@ const assertCreateGroupSucceeds = function( memberIds, callback ) { - const AuthzTestUtil = require('oae-authz/lib/test/util'); assertGetMeSucceeds(restContext, me => { const roleChanges = _.extend( AuthzTestUtil.createRoleChange(managerIds, 'manager'), @@ -504,7 +504,7 @@ const assertCreateGroupSucceeds = function( if (err) { return callback(err); } - const AuthzTestUtil = require('oae-authz/lib/test/util'); + // Ensure the members and managers are as we would expect for members and invitations assertGetAllMembersLibrarySucceeds(restContext, group.id, null, results => { AuthzTestUtil.assertMemberRolesEquals({}, roleChanges, AuthzTestUtil.getMemberRolesFromResults(results)); @@ -977,8 +977,6 @@ const assertUploadGroupPictureSucceeds = function(restCtx, groupId, opts, callba * @throws {AssertionError} Thrown if any of the assertions fail */ const assertSetGroupMembersSucceeds = function(managerRestContext, actorRestContext, groupId, members, callback) { - const AuthzTestUtil = require('oae-authz/lib/test/util'); - assertGetAllMembersLibrarySucceeds(managerRestContext, groupId, null, results => { const memberRolesBefore = AuthzTestUtil.getMemberRolesFromResults(results); AuthzTestUtil.assertGetInvitationsSucceeds(managerRestContext, 'group', groupId, result => { @@ -1220,6 +1218,7 @@ const assertMembershipsLibrariesEquals = function(restCtx, expectedMemberships, if (!_userIds) { return assertMembershipsLibrariesEquals(restCtx, expectedMemberships, callback, _.keys(expectedMemberships)); } + if (_.isEmpty(_userIds)) { return callback(); } @@ -2268,7 +2267,7 @@ const assertUpdateJoinGroupByRequestFails = function(restCtx, groupId, principal }); }; -module.exports = { +export { importUsers, addUserToAllGroups, updateAllGroups, diff --git a/packages/oae-principals/lib/util.js b/packages/oae-principals/lib/util.js index dfd47ff4ae..e3b5f075fe 100644 --- a/packages/oae-principals/lib/util.js +++ b/packages/oae-principals/lib/util.js @@ -13,20 +13,20 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const shortid = require('shortid'); +import _ from 'underscore'; +import shortid from 'shortid'; -const { ActivityConstants } = require('oae-activity/lib/constants'); -const ActivityModel = require('oae-activity/lib/model'); -const { AuthzConstants } = require('oae-authz/lib/constants'); -const AuthzUtil = require('oae-authz/lib/util'); -const ContentUtil = require('oae-content/lib/internal/util'); -const TenantsUtil = require('oae-tenants/lib/util'); +import { ActivityConstants } from 'oae-activity/lib/constants'; +import { AuthzConstants } from 'oae-authz/lib/constants'; +import { PrincipalsConstants } from 'oae-principals/lib/constants'; -const { PrincipalsConstants } = require('oae-principals/lib/constants'); -const PrincipalsEmitter = require('oae-principals/lib/internal/emitter'); -const PrincipalsDAO = require('./internal/dao'); -const { User } = require('./model'); +import * as ActivityModel from 'oae-activity/lib/model'; +import * as AuthzUtil from 'oae-authz/lib/util'; +import * as ContentUtil from 'oae-content/lib/internal/util'; +import * as TenantsUtil from 'oae-tenants/lib/util'; +import PrincipalsEmitter from 'oae-principals/lib/internal/emitter'; +import { User } from './model'; +import * as PrincipalsDAO from './internal/dao'; /** * Get a principal (user or group) @@ -42,6 +42,7 @@ const getPrincipal = function(ctx, principalId, callback) { if (err) { return callback(err); } + if (!principals[principalId]) { return callback({ code: 404, msg: 'Could not find principal with id ' + principalId }); } @@ -82,7 +83,7 @@ const getPrincipals = function(ctx, principalIds, callback) { * @param {Principal} callback.updatedPrincipal The updated version of the principal with its last modifed date updated */ const touchLastModified = function(oldPrincipal, callback) { - // const oldLastModified = oldPrincipal.lastModified; + // Const oldLastModified = oldPrincipal.lastModified; const newLastModified = Date.now().toString(); const updatedProfileFields = { lastModified: newLastModified }; PrincipalsDAO.updatePrincipal(oldPrincipal.id, updatedProfileFields, err => { @@ -194,6 +195,7 @@ const hideUserData = function(ctx, user) { if (!invalid.test(user.publicAlias)) { user.displayName = user.publicAlias; } + user.picture = {}; // The profile path should be removed from the user object as well. This will tell the UI @@ -320,6 +322,7 @@ const transformPersistentUserActivityEntityToInternal = function(ctx, userId, us _generatePictureURLs(ctx, user, -1); return user; } + return { id: userId }; }; @@ -408,6 +411,7 @@ const transformPersistentGroupActivityEntityToInternal = function(ctx, groupId, _generatePictureURLs(ctx, group, -1); return group; } + return { id: groupId }; }; @@ -439,37 +443,22 @@ const _transformPrincipals = function(ctx, principals) { */ const _generatePictureURLs = function(ctx, principal, duration, offset) { if (principal.picture.smallUri) { - principal.picture.small = ContentUtil.getSignedDownloadUrl( - ctx, - principal.picture.smallUri, - duration, - offset - ); + principal.picture.small = ContentUtil.getSignedDownloadUrl(ctx, principal.picture.smallUri, duration, offset); delete principal.picture.smallUri; } if (principal.picture.mediumUri) { - principal.picture.medium = ContentUtil.getSignedDownloadUrl( - ctx, - principal.picture.mediumUri, - duration, - offset - ); + principal.picture.medium = ContentUtil.getSignedDownloadUrl(ctx, principal.picture.mediumUri, duration, offset); delete principal.picture.mediumUri; } if (principal.picture.largeUri) { - principal.picture.large = ContentUtil.getSignedDownloadUrl( - ctx, - principal.picture.largeUri, - duration, - offset - ); + principal.picture.large = ContentUtil.getSignedDownloadUrl(ctx, principal.picture.largeUri, duration, offset); delete principal.picture.largeUri; } }; -module.exports = { +export { getPrincipal, getPrincipals, touchLastModified, diff --git a/packages/oae-principals/tests/test-activity.js b/packages/oae-principals/tests/test-activity.js index dcbac635ab..473ad1f71e 100644 --- a/packages/oae-principals/tests/test-activity.js +++ b/packages/oae-principals/tests/test-activity.js @@ -26,7 +26,7 @@ import * as RestAPI from 'oae-rest'; import * as RestUtil from 'oae-rest/lib/util'; import * as TestsUtil from 'oae-tests'; -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; describe('Principals Activity', () => { // Rest context that can be used every time we need to make a request as an anonymous user diff --git a/packages/oae-principals/tests/test-export-data.js b/packages/oae-principals/tests/test-export-data.js index 132da2100f..ceeac1b984 100644 --- a/packages/oae-principals/tests/test-export-data.js +++ b/packages/oae-principals/tests/test-export-data.js @@ -24,7 +24,7 @@ import * as ContentsTestUtil from 'oae-content/lib/test/util'; import * as Etherpad from 'oae-content/lib/internal/etherpad'; import * as RestAPI from 'oae-rest'; import * as TestsUtil from 'oae-tests'; -import * as PrincipalsAPI from 'oae-principals'; +import PrincipalsAPI from 'oae-principals'; describe('Export data', () => { // Rest contexts that can be used to make requests as different types of users diff --git a/packages/oae-principals/tests/test-groups.js b/packages/oae-principals/tests/test-groups.js index 3fe4ff58a6..609196b16c 100644 --- a/packages/oae-principals/tests/test-groups.js +++ b/packages/oae-principals/tests/test-groups.js @@ -27,7 +27,7 @@ import * as LibraryTestUtil from 'oae-library/lib/test/util'; import * as RestAPI from 'oae-rest'; import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; import * as TestsUtil from 'oae-tests'; -import * as PrincipalsAPI from 'oae-principals'; +import PrincipalsAPI from 'oae-principals'; import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; import { AuthzConstants } from 'oae-authz/lib/constants'; diff --git a/packages/oae-principals/tests/test-users.js b/packages/oae-principals/tests/test-users.js index a18c4bb387..88640134c6 100644 --- a/packages/oae-principals/tests/test-users.js +++ b/packages/oae-principals/tests/test-users.js @@ -27,7 +27,7 @@ import * as RestAPI from 'oae-rest'; import * as TenantsTestUtil from 'oae-tenants/lib/test/util'; import * as TestsUtil from 'oae-tests'; import * as TZ from 'oae-util/lib/tz'; -import * as PrincipalsAPI from 'oae-principals'; +import PrincipalsAPI from 'oae-principals'; import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; import { RestContext } from 'oae-rest/lib/model'; diff --git a/packages/oae-rest b/packages/oae-rest index e417f8615a..efc38a5498 160000 --- a/packages/oae-rest +++ b/packages/oae-rest @@ -1 +1 @@ -Subproject commit e417f8615ab4d603c188f6eb1ea39a593e85ad5b +Subproject commit efc38a5498a451e74c94348fbf34ca245bcc5550 diff --git a/packages/oae-tests/lib/util.js b/packages/oae-tests/lib/util.js index 7c5c48cdec..9f3eb544c0 100644 --- a/packages/oae-tests/lib/util.js +++ b/packages/oae-tests/lib/util.js @@ -41,7 +41,7 @@ import * as OAE from 'oae-util/lib/oae'; import * as OaeUtil from 'oae-util/lib/util'; import * as PreviewAPI from 'oae-preview-processor/lib/api'; import PreviewConstants from 'oae-preview-processor/lib/constants'; -import * as PrincipalsAPI from 'oae-principals'; +import PrincipalsAPI from 'oae-principals'; import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; import * as Redis from 'oae-util/lib/redis'; import * as RestAPI from 'oae-rest'; diff --git a/packages/oae-util/lib/cassandra.js b/packages/oae-util/lib/cassandra.js index fe8650e360..3902ed8d79 100644 --- a/packages/oae-util/lib/cassandra.js +++ b/packages/oae-util/lib/cassandra.js @@ -13,16 +13,22 @@ * permissions and limitations under the License. */ -const util = require('util'); -const cassandra = require('cassandra-driver'); -const { Row, dataTypes } = require('cassandra-driver').types; -const _ = require('underscore'); +import util from 'util'; + +import * as cassandra from 'cassandra-driver'; // eslint-disable-next-line no-unused-vars -const OAE = require('oae-util/lib/oae'); +import * as OAE from 'oae-util/lib/oae'; + +import { logger } from 'oae-logger'; +import { telemetry } from 'oae-telemetry'; +import * as OaeUtil from 'oae-util/lib/util'; + +const { Row, dataTypes } = cassandra.types; +const _ = require('underscore'); + +const log = logger('oae-cassandra'); -const log = require('oae-logger').logger('oae-cassandra'); -const OaeUtil = require('oae-util/lib/util'); -const Telemetry = require('oae-telemetry').telemetry('cassandra'); +const Telemetry = telemetry('cassandra'); const DEFAULT_ITERATEALL_BATCH_SIZE = 100; let CONFIG = null; @@ -994,7 +1000,7 @@ const _truncateString = function(str, ifOverSize) { return str; }; -module.exports = { +export { init, close, createKeyspace, diff --git a/packages/oae-util/lib/cleaner.js b/packages/oae-util/lib/cleaner.js index a9b6a6ccbb..207cea348e 100644 --- a/packages/oae-util/lib/cleaner.js +++ b/packages/oae-util/lib/cleaner.js @@ -13,12 +13,14 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const path = require('path'); -const _ = require('underscore'); +import fs from 'fs'; +import path from 'path'; +import _ from 'underscore'; -const EmitterAPI = require('oae-emitter'); -const log = require('oae-logger').logger('oae-cleaner'); +import * as EmitterAPI from 'oae-emitter'; +import { logger } from 'oae-logger'; + +const log = logger('oae-cleaner'); const cleaners = {}; @@ -28,7 +30,7 @@ const cleaners = {}; * * `cleaned(directory)` - A clean cycle just finished on a directory. The `directory` is provided as an event parameter. */ const Cleaner = new EmitterAPI.EventEmitter(); -module.exports.emitter = Cleaner; +export { Cleaner as emitter }; /** * Starts a cleaning job. @@ -103,6 +105,7 @@ const checkFile = function(path, time, callback) { // If we get an error that is not a "no such file"-error, something is probably wrong } + if (err) { log().error({ err, path }, 'Could not get the metadata for a file.'); return callback(err); @@ -110,16 +113,14 @@ const checkFile = function(path, time, callback) { // Only try to unlink file resources that have expired if (stats && stats.isFile() && stats.atime.getTime() < time) { - log().info( - { path, lastModified: stats.atime.getTime(), expires: time }, - 'Deleting expired temporary file.' - ); + log().info({ path, lastModified: stats.atime.getTime(), expires: time }, 'Deleting expired temporary file.'); fs.unlink(path, err => { // Only report the error if it's not a "no such file"-error if (err && err.code !== 'ENOENT') { log().error({ err, path }, 'Could not delete an expired temporary file.'); return callback(err); } + callback(); }); } else { @@ -152,8 +153,4 @@ const checkFiles = function(paths, time, callback) { }); }; -module.exports = { - emitter: Cleaner, - start, - stop -}; +export { start, stop }; diff --git a/packages/oae-util/lib/counter.js b/packages/oae-util/lib/counter.js index 5da8c2cf59..a8d7c5275e 100644 --- a/packages/oae-util/lib/counter.js +++ b/packages/oae-util/lib/counter.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const EmitterAPI = require('oae-emitter'); +import * as EmitterAPI from 'oae-emitter'; /** * A utility structure that allows one to increment and decrement a count, firing bound handlers @@ -87,4 +87,4 @@ const Counter = function() { return that; }; -module.exports = Counter; +export default Counter; diff --git a/packages/oae-util/lib/emitter.js b/packages/oae-util/lib/emitter.js index 3a45f67911..740c35cfbd 100644 --- a/packages/oae-util/lib/emitter.js +++ b/packages/oae-util/lib/emitter.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const EmitterAPI = require('oae-emitter'); +import * as EmitterAPI from 'oae-emitter'; /** * ### Events @@ -21,4 +21,4 @@ const EmitterAPI = require('oae-emitter'); * * `ready` - Emitted when server start-up has completed and the application is ready to start accepting requests */ const emitter = new EmitterAPI.EventEmitter(); -module.exports = emitter; +export default emitter; diff --git a/packages/oae-util/lib/image.js b/packages/oae-util/lib/image.js index 424f20e7d6..59324dc70f 100644 --- a/packages/oae-util/lib/image.js +++ b/packages/oae-util/lib/image.js @@ -13,16 +13,20 @@ * permissions and limitations under the License. */ -const { exec } = require('child_process'); -const fs = require('fs'); -const path = require('path'); -const util = require('util'); -const gm = require('gm'); -const temp = require('temp'); -const _ = require('underscore'); +import { exec } from 'child_process'; -const log = require('oae-logger').logger('oae-util-image'); -const { Validator } = require('oae-util/lib/validator'); +import fs from 'fs'; +import path from 'path'; +import util from 'util'; +import gm from 'gm'; +import temp from 'temp'; +import _ from 'underscore'; + +import { logger } from 'oae-logger'; + +import { Validator } from 'oae-util/lib/validator'; + +const log = logger('oae-util-image'); const VALID_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif']; @@ -56,6 +60,7 @@ const autoOrient = function(inputPath, opts, callback) { log().error({ err }, 'Could not auto orient the image %s', inputPath); return callback({ code: 500, msg: 'Could not auto orient the image' }); } + fs.stat(outputPath, (err, stat) => { if (err) { fs.unlink(outputPath, () => { @@ -69,6 +74,7 @@ const autoOrient = function(inputPath, opts, callback) { msg: 'Could not retrieve the file information for the cropped file' }); } + const file = { path: outputPath, size: stat.size, @@ -108,9 +114,7 @@ const autoOrient = function(inputPath, opts, callback) { */ const cropAndResize = function(imagePath, selectedArea, sizes, callback) { const validator = new Validator(); - validator - .check(imagePath, { code: 400, msg: 'A path to the image that you want to crop is missing' }) - .notNull(); + validator.check(imagePath, { code: 400, msg: 'A path to the image that you want to crop is missing' }).notNull(); validator .check(null, { code: 400, @@ -118,18 +122,10 @@ const cropAndResize = function(imagePath, selectedArea, sizes, callback) { }) .isObject(selectedArea); if (selectedArea) { - validator - .check(selectedArea.x, { code: 400, msg: 'The x-coordinate needs to be a valid integer' }) - .isInt(); - validator - .check(selectedArea.x, { code: 400, msg: 'The x-coordinate needs to be a valid integer' }) - .min(0); - validator - .check(selectedArea.y, { code: 400, msg: 'The y-coordinate needs to be a valid integer' }) - .isInt(); - validator - .check(selectedArea.y, { code: 400, msg: 'The y-coordinate needs to be a valid integer' }) - .min(0); + validator.check(selectedArea.x, { code: 400, msg: 'The x-coordinate needs to be a valid integer' }).isInt(); + validator.check(selectedArea.x, { code: 400, msg: 'The x-coordinate needs to be a valid integer' }).min(0); + validator.check(selectedArea.y, { code: 400, msg: 'The y-coordinate needs to be a valid integer' }).isInt(); + validator.check(selectedArea.y, { code: 400, msg: 'The y-coordinate needs to be a valid integer' }).min(0); validator .check(selectedArea.width, { code: 400, @@ -155,6 +151,7 @@ const cropAndResize = function(imagePath, selectedArea, sizes, callback) { }) .min(1); } + validator.check(sizes, { code: 400, msg: 'The desired sizes array is missing' }).notNull(); if (sizes) { validator.check(sizes.length, { code: 400, msg: 'The desired sizes array is empty' }).min(1); @@ -185,6 +182,7 @@ const cropAndResize = function(imagePath, selectedArea, sizes, callback) { .min(0); } } + if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -194,6 +192,7 @@ const cropAndResize = function(imagePath, selectedArea, sizes, callback) { if (err) { return callback(err); } + const files = {}; let resized = 0; let called = false; @@ -246,9 +245,7 @@ const cropAndResize = function(imagePath, selectedArea, sizes, callback) { */ const cropImage = function(imagePath, selectedArea, callback) { const validator = new Validator(); - validator - .check(imagePath, { code: 400, msg: 'A path to the image that you want to crop is missing' }) - .notNull(); + validator.check(imagePath, { code: 400, msg: 'A path to the image that you want to crop is missing' }).notNull(); validator .check(null, { code: 400, @@ -256,18 +253,10 @@ const cropImage = function(imagePath, selectedArea, callback) { }) .isObject(selectedArea); if (selectedArea) { - validator - .check(selectedArea.x, { code: 400, msg: 'The x-coordinate needs to be a valid integer' }) - .isInt(); - validator - .check(selectedArea.x, { code: 400, msg: 'The x-coordinate needs to be a valid integer' }) - .min(0); - validator - .check(selectedArea.y, { code: 400, msg: 'The y-coordinate needs to be a valid integer' }) - .isInt(); - validator - .check(selectedArea.y, { code: 400, msg: 'The y-coordinate needs to be a valid integer' }) - .min(0); + validator.check(selectedArea.x, { code: 400, msg: 'The x-coordinate needs to be a valid integer' }).isInt(); + validator.check(selectedArea.x, { code: 400, msg: 'The x-coordinate needs to be a valid integer' }).min(0); + validator.check(selectedArea.y, { code: 400, msg: 'The y-coordinate needs to be a valid integer' }).isInt(); + validator.check(selectedArea.y, { code: 400, msg: 'The y-coordinate needs to be a valid integer' }).min(0); validator .check(selectedArea.width, { code: 400, @@ -293,9 +282,11 @@ const cropImage = function(imagePath, selectedArea, callback) { }) .min(1); } + if (validator.hasErrors()) { return callback(validator.getFirstError()); } + _cropImage(imagePath, selectedArea, callback); }; @@ -349,6 +340,7 @@ const _cropImage = function(imagePath, selectedArea, callback) { log().error({ err }, 'Could not crop the image %s', imagePath); return callback({ code: 500, msg: 'Could not crop the image' }); } + fs.stat(tempPath, (err, stat) => { if (err) { fs.unlink(tempPath, () => { @@ -362,6 +354,7 @@ const _cropImage = function(imagePath, selectedArea, callback) { msg: 'Could not retrieve the file information for the cropped file' }); } + const file = { path: tempPath, size: stat.size, @@ -390,17 +383,11 @@ const _cropImage = function(imagePath, selectedArea, callback) { */ const resizeImage = function(imagePath, size, callback) { const validator = new Validator(); - validator - .check(imagePath, { code: 400, msg: 'A path to the image that you want to resize is missing' }) - .notNull(); + validator.check(imagePath, { code: 400, msg: 'A path to the image that you want to resize is missing' }).notNull(); validator.check(null, { code: 400, msg: 'The size must be specified' }).isObject(size); if (size) { - validator - .check(size.width, { code: 400, msg: 'The width needs to be a valid integer larger than 0' }) - .isInt(); - validator - .check(size.width, { code: 400, msg: 'The width needs to be a valid integer larger than 0' }) - .min(0); + validator.check(size.width, { code: 400, msg: 'The width needs to be a valid integer larger than 0' }).isInt(); + validator.check(size.width, { code: 400, msg: 'The width needs to be a valid integer larger than 0' }).min(0); validator .check(size.height, { code: 400, @@ -414,9 +401,11 @@ const resizeImage = function(imagePath, size, callback) { }) .min(0); } + if (validator.hasErrors()) { return callback(validator.getFirstError()); } + _resizeImage(imagePath, size, callback); }; @@ -451,6 +440,7 @@ const _resizeImage = function(imagePath, size, callback) { log().error({ err }, 'Could not resize the image %s', imagePath); return callback({ code: 500, msg: 'Could not resize the image' }); } + fs.stat(tempPath, (err, stat) => { if (err) { fs.unlink(tempPath, () => { @@ -464,6 +454,7 @@ const _resizeImage = function(imagePath, size, callback) { msg: 'Could not get the file information for the resized file' }); } + const file = { path: tempPath, size: stat.size, @@ -489,6 +480,7 @@ const getImageExtension = function(source, fallback) { if (!_.contains(VALID_EXTENSIONS, ext)) { ext = fallback; } + return ext; }; @@ -508,9 +500,7 @@ const getImageExtension = function(source, fallback) { */ const convertToJPG = function(inputPath, callback) { const validator = new Validator(); - validator - .check(inputPath, { code: 400, msg: 'A path to the image that you want to resize is missing' }) - .notNull(); + validator.check(inputPath, { code: 400, msg: 'A path to the image that you want to resize is missing' }).notNull(); if (validator.hasErrors()) { return callback(validator.getFirstError()); } @@ -520,6 +510,7 @@ const convertToJPG = function(inputPath, callback) { // If we're dealing with a GIF, we use the first frame conversionPath = inputPath + '[0]'; } + gm(conversionPath).size((err, size) => { if (err) { log().error({ err }, 'Unable to get size of the image that should be converted to JPG'); @@ -527,15 +518,15 @@ const convertToJPG = function(inputPath, callback) { } /*! - * The below command is responsible for generating a somewhat decent looking JPG. - * We superimpose the original image over a white image of the same size to ensure - * that formats which can contain transparant pixels look reasonably well in a JPG. - * - * gm convert -size 220x276 xc:white -compose over img.png -flatten flattened.jpg - * - * There doesn't seem to be a good way to execute the proper command with the `gm` module, - * so we have to do it manually here. - */ + * The below command is responsible for generating a somewhat decent looking JPG. + * We superimpose the original image over a white image of the same size to ensure + * that formats which can contain transparant pixels look reasonably well in a JPG. + * + * gm convert -size 220x276 xc:white -compose over img.png -flatten flattened.jpg + * + * There doesn't seem to be a good way to execute the proper command with the `gm` module, + * so we have to do it manually here. + */ const outputPath = temp.path({ suffix: '.jpg' }); const cmd = util.format( 'gm convert -size %dx%d xc:white -compose over %s -flatten %s', @@ -569,6 +560,7 @@ const convertToJPG = function(inputPath, callback) { msg: 'Could not retrieve the file information for the converted file' }); } + const file = { path: outputPath, size: stat.size, @@ -580,11 +572,4 @@ const convertToJPG = function(inputPath, callback) { }); }; -module.exports = { - autoOrient, - cropAndResize, - cropImage, - resizeImage, - getImageExtension, - convertToJPG -}; +export { autoOrient, cropAndResize, cropImage, resizeImage, getImageExtension, convertToJPG }; diff --git a/packages/oae-util/lib/init.js b/packages/oae-util/lib/init.js index 93927a53c5..bbea8eb6b4 100644 --- a/packages/oae-util/lib/init.js +++ b/packages/oae-util/lib/init.js @@ -13,19 +13,21 @@ * permissions and limitations under the License. */ -const log = require('oae-logger').logger('oae-cassandra'); - -const Cassandra = require('./cassandra'); -const Cleaner = require('./cleaner'); -const Locking = require('./locking'); -const MQ = require('./mq'); -const Pubsub = require('./pubsub'); -const Redis = require('./redis'); -const Signature = require('./signature'); -const TaskQueue = require('./taskqueue'); -const Tempfile = require('./tempfile'); - -const init = function(config, callback) { +import { logger } from 'oae-logger'; + +import * as Cassandra from './cassandra'; +import * as Cleaner from './cleaner'; +import * as Locking from './locking'; +import * as MQ from './mq'; +import * as Pubsub from './pubsub'; +import * as Redis from './redis'; +import * as Signature from './signature'; +import * as TaskQueue from './taskqueue'; +import * as Tempfile from './tempfile'; + +const log = logger('oae-cassandra'); + +export const init = function(config, callback) { // Create Cassandra database. // TODO: Move Cassandra into its own oae-cassandra module with a high priority. All of the init(..) stuff then goes in its init.js bootCassandra(config, () => { @@ -46,8 +48,10 @@ const bootCassandra = (config, callback) => { log().error('Error connecting to cassandra, retrying in ' + timeout + 's...'); return setTimeout(Cassandra.init, timeout * 1000, config.cassandra, retryCallback); } + return callback(); }; + Cassandra.init(config.cassandra, retryCallback); }; @@ -58,6 +62,7 @@ const bootRedis = (config, callback) => { if (err) { return callback(err); } + // Initialize the Redis based locking Locking.init(); @@ -100,5 +105,3 @@ const bootRabbitMQ = (config, callback) => { TaskQueue.init(callback); }); }; - -module.exports = init; diff --git a/packages/oae-util/lib/internal/globals.js b/packages/oae-util/lib/internal/globals.js index 6b570f6c32..b1d39f514b 100644 --- a/packages/oae-util/lib/internal/globals.js +++ b/packages/oae-util/lib/internal/globals.js @@ -15,4 +15,4 @@ // Global Underscore.js mixins // eslint-disable-next-line import/no-unassigned-import -require('./globals/underscore'); +import * as _ from './globals/underscore'; diff --git a/packages/oae-util/lib/internal/globals/underscore.js b/packages/oae-util/lib/internal/globals/underscore.js index a94db13356..6a19f47281 100644 --- a/packages/oae-util/lib/internal/globals/underscore.js +++ b/packages/oae-util/lib/internal/globals/underscore.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; /*! * All OAE-specific mixins are prefixed with `oae`, followed by a short name that is descriptive to @@ -31,6 +31,7 @@ _.mixin({ if (!source) { return undefined; } + return source[key]; }, diff --git a/packages/oae-util/lib/internal/shutdown.js b/packages/oae-util/lib/internal/shutdown.js index c3c1f17c3a..3da571f905 100644 --- a/packages/oae-util/lib/internal/shutdown.js +++ b/packages/oae-util/lib/internal/shutdown.js @@ -13,8 +13,10 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const log = require('oae-logger').logger('oae-util-shutdown'); +import _ from 'underscore'; +import { logger } from 'oae-logger'; + +const log = logger('oae-util-shutdown'); // Variables that track the shutdown status of the system const preShutdownHandlers = {}; @@ -89,10 +91,7 @@ const _preShutdown = function(defaultTimeoutMillis, callback) { }; // Set a timeout and invoke the handler. Whichever finishes first will tell _monitorPreShutdown they have finished. - timeoutHandle = setTimeout( - _monitorPreShutdown, - handlerInfo.maxTimeMillis || defaultTimeoutMillis - ); + timeoutHandle = setTimeout(_monitorPreShutdown, handlerInfo.maxTimeMillis || defaultTimeoutMillis); handlerInfo.handler(_monitorPreShutdown); }); }; @@ -106,7 +105,4 @@ const _exit = function() { process.exit(); }; -module.exports = { - registerPreShutdownHandler, - shutdown -}; +export { registerPreShutdownHandler, shutdown }; diff --git a/packages/oae-util/lib/io.js b/packages/oae-util/lib/io.js index 36dc01187e..9a32c85a91 100644 --- a/packages/oae-util/lib/io.js +++ b/packages/oae-util/lib/io.js @@ -13,9 +13,11 @@ * permissions and limitations under the License. */ -const fs = require('fs'); +import fs from 'fs'; -const log = require('oae-logger').logger('IO'); +import { logger } from 'oae-logger'; + +const log = logger('IO'); /** * Get a list of all of the files and folders inside of a folder. Hidden files and folder (starting with @@ -31,6 +33,7 @@ const getFileListForFolder = function(foldername, callback) { if (err) { return callback(null, []); } + if (!stat.isDirectory()) { return callback(null, []); } @@ -101,10 +104,12 @@ const moveFile = function(source, dest, callback) { log().error({ err }, "Wasn't able to rename the file %s to %s.", source, dest); return callback({ code: 500, msg: err }); } + copyFile(source, dest, err => { if (err) { return callback({ code: 500, msg: err }); } + fs.unlink(source, callback); }); } else { @@ -141,6 +146,7 @@ const exists = function(path, callback) { if (err.code === 'ENOENT') { return callback(null, false); } + log().error( { err, @@ -150,14 +156,9 @@ const exists = function(path, callback) { ); return callback({ code: 500, msg: 'Could not check whether a file or folder exists' }); } + return callback(null, true); }); }; -module.exports = { - getFileListForFolder, - copyFile, - moveFile, - destroyStream, - exists -}; +export { getFileListForFolder, copyFile, moveFile, destroyStream, exists }; diff --git a/packages/oae-util/lib/locking.js b/packages/oae-util/lib/locking.js index d1318923fe..d3e7b97626 100644 --- a/packages/oae-util/lib/locking.js +++ b/packages/oae-util/lib/locking.js @@ -13,11 +13,14 @@ * permissions and limitations under the License. */ -const redback = require('redback'); +import redback from 'redback'; -const log = require('oae-logger').logger('oae-util-locking'); -const Redis = require('./redis'); -const { Validator } = require('./validator'); +import { logger } from 'oae-logger'; + +import * as Redis from './redis'; +import { Validator } from './validator'; + +const log = logger('oae-util-locking'); let lock = null; @@ -89,8 +92,7 @@ const release = function(lockKey, token, callback) { validator .check(token, { code: 400, - msg: - 'The identifier of the lock that was given when the lock was acquired needs to be specified' + msg: 'The identifier of the lock that was given when the lock was acquired needs to be specified' }) .notNull(); if (validator.hasErrors()) { @@ -100,8 +102,4 @@ const release = function(lockKey, token, callback) { lock.release(lockKey, token, callback); }; -module.exports = { - init, - acquire, - release -}; +export { init, acquire, release }; diff --git a/packages/oae-util/lib/middleware/multipart.js b/packages/oae-util/lib/middleware/multipart.js index c7c4de590a..b44e9bd28a 100644 --- a/packages/oae-util/lib/middleware/multipart.js +++ b/packages/oae-util/lib/middleware/multipart.js @@ -13,10 +13,12 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const multiparty = require('multiparty'); +import _ from 'underscore'; +import multiparty from 'multiparty'; -const log = require('oae-logger').logger('oae-util-multipart'); +import { logger } from 'oae-logger'; + +const log = logger('oae-util-multipart'); /** * Get the multipart file upload request parser middleware for Express. The middleware only has an effect if it @@ -35,7 +37,7 @@ const log = require('oae-logger').logger('oae-util-multipart'); * @param {String} [formOptions.uploadDir] The temporary directory to use to store the uploaded file * @return {Function} An express middleware handler that will parse the request body if it is multipart/form-data */ -module.exports = function(formOptions) { +export default function(formOptions) { formOptions = formOptions || {}; formOptions = { autoFiles: true, @@ -43,8 +45,8 @@ module.exports = function(formOptions) { }; /*! - * Provide the middleware handler as per the export summary - */ + * Provide the middleware handler as per the export summary + */ return function(req, res, next) { if (req._body) { // The request has already been parsed, don't try to handle it @@ -59,6 +61,7 @@ module.exports = function(formOptions) { // Ignore GET and HEAD requests return next(); } + if (!req.is('multipart/form-data')) { // Only handle multipart/form-data requests return next(); @@ -103,7 +106,9 @@ module.exports = function(formOptions) { return next(); }); }; -}; +} + + /** * Multiparty always supplies its values as arrays. To be consistent with other request parsers, diff --git a/packages/oae-util/lib/modules.js b/packages/oae-util/lib/modules.js index 0d69b11ed6..dcbebc6874 100644 --- a/packages/oae-util/lib/modules.js +++ b/packages/oae-util/lib/modules.js @@ -20,7 +20,7 @@ import _ from 'underscore'; import { logger } from 'oae-logger'; import * as OaeUtil from 'oae-util/lib/util'; import * as IO from './io'; -import Swagger from './swagger'; +import * as Swagger from './swagger'; const log = logger('oae-modules'); @@ -51,7 +51,9 @@ const ES6Modules = [ 'oae-tincanapi', 'oae-preview-processor', 'oae-search', - 'oae-tenants' + 'oae-tenants', + 'oae-util', + 'oae-principals' ]; /// /////////////////////// diff --git a/packages/oae-util/lib/mq.js b/packages/oae-util/lib/mq.js index f967d56df0..1a0163956f 100644 --- a/packages/oae-util/lib/mq.js +++ b/packages/oae-util/lib/mq.js @@ -13,14 +13,16 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); -const amqp = require('amqp-connection-manager'); +import util from 'util'; +import _ from 'underscore'; +import amqp from 'amqp-connection-manager'; -const EmitterAPI = require('oae-emitter'); -const log = require('oae-logger').logger('mq'); -const OAE = require('./oae'); -const OaeEmitter = require('./emitter'); +import { logger } from 'oae-logger'; +import * as EmitterAPI from 'oae-emitter'; +import OaeEmitter from './emitter'; +import * as OAE from './oae'; + +const log = logger('mq'); const MqConstants = { REDELIVER_EXCHANGE_NAME: 'oae-util-mq-redeliverexchange', @@ -81,7 +83,6 @@ const NUM_MESSAGES_TO_DUMP = 10; * * `storedRedelivery(queueName, data, headers, deliveryInfo)` - Invoked when a redelivered message has been aborted and stored in the redelivery queue to be manually intervened */ const MQ = new EmitterAPI.EventEmitter(); -module.exports.emitter = MQ; const deferredTaskHandlers = {}; // Let ready = false; @@ -102,6 +103,7 @@ OaeEmitter.on('ready', () => { // Do nothing, we've called back return; } + if (err) { MQ.emit('ready', err); returned = true; @@ -154,6 +156,7 @@ const init = function(mqConfig, callback) { log().warn('Attempted to initialize an existing RabbitMQ connector. Ignoring'); return _createRedeliveryQueue(callback); } + log().info('Initializing RabbitMQ connector'); const arrayOfHostsToConnectTo = _.map(mqConfig.connection.host, eachHost => { @@ -246,6 +249,7 @@ const declareExchange = function(exchangeName, exchangeOptions, callback) { msg: 'Tried to declare an exchange without providing an exchange name' }); } + if (exchanges[exchangeName]) { log().error({ exchangeName, err: new Error('Tried to declare an exchange twice') }); return callback({ code: 400, msg: 'Tried to declare an exchange twice' }); @@ -278,6 +282,7 @@ const declareQueue = function(queueName, queueOptions, callback) { }); return callback({ code: 400, msg: 'Tried to declare a queue without providing a name' }); } + if (queues[queueName]) { log().error({ queueName, queueOptions, err: new Error('Tried to declare a queue twice') }); return callback({ code: 400, msg: 'Tried to declare a queue twice' }); @@ -389,37 +394,33 @@ const bindQueueToExchange = function(queueName, exchangeName, routingKey, callba queueName, exchangeName, routingKey, - err: new Error( - 'Tried to bind a non existing queue to an exchange, have you declared it first?' - ) + err: new Error('Tried to bind a non existing queue to an exchange, have you declared it first?') }); return callback({ code: 400, msg: 'Tried to bind a non existing queue to an exchange, have you declared it first?' }); } + if (!exchanges[exchangeName]) { log().error({ queueName, exchangeName, routingKey, - err: new Error( - 'Tried to bind a queue to a non-existing exchange, have you declared it first?' - ) + err: new Error('Tried to bind a queue to a non-existing exchange, have you declared it first?') }); return callback({ code: 400, msg: 'Tried to bind a queue to a non-existing exchange, have you declared it first?' }); } + if (!routingKey) { log().error({ queueName, exchangeName, routingKey, - err: new Error( - 'Tried to bind a queue to an existing exchange without specifying a routing key' - ) + err: new Error('Tried to bind a queue to an existing exchange without specifying a routing key') }); return callback({ code: 400, msg: 'Missing routing key' }); } @@ -450,29 +451,27 @@ const unbindQueueFromExchange = function(queueName, exchangeName, routingKey, ca queueName, exchangeName, routingKey, - err: new Error( - 'Tried to unbind a non existing queue from an exchange, have you declared it first?' - ) + err: new Error('Tried to unbind a non existing queue from an exchange, have you declared it first?') }); return callback({ code: 400, msg: 'Tried to unbind a non existing queue from an exchange, have you declared it first?' }); } + if (!exchanges[exchangeName]) { log().error({ queueName, exchangeName, routingKey, - err: new Error( - 'Tried to unbind a queue from a non-existing exchange, have you declared it first?' - ) + err: new Error('Tried to unbind a queue from a non-existing exchange, have you declared it first?') }); return callback({ code: 400, msg: 'Tried to unbind a queue from a non-existing exchange, have you declared it first?' }); } + if (!routingKey) { log().error({ queueName, @@ -506,10 +505,7 @@ const subscribeQueue = function(queueName, subscribeOptions, listener, callback) callback || function(err) { if (err) { - log().warn( - { err, queueName, subscribeOptions }, - 'An error occurred while subscribing to a queue' - ); + log().warn({ err, queueName, subscribeOptions }, 'An error occurred while subscribing to a queue'); } }; @@ -559,10 +555,7 @@ const subscribeQueue = function(queueName, subscribeOptions, listener, callback) MqConstants.REDELIVER_SUBMIT_OPTIONS, err => { if (err) { - log().warn( - { err }, - 'An error occurred delivering a redelivered message to the redelivery queue' - ); + log().warn({ err }, 'An error occurred delivering a redelivered message to the redelivery queue'); } MQ.emit('storedRedelivery', queueName, data, headers, deliveryInfo, msg); @@ -664,6 +657,7 @@ const unsubscribeQueue = function(queueName, callback) { }); return callback(); } + const { consumerTag } = queue; queue = queue.queue; @@ -735,6 +729,7 @@ const submit = function(exchangeName, routingKey, data, options, callback) { }); return callback({ code: 400, msg: 'Tried to submit a message to an unknown exchange' }); } + if (!routingKey) { log().error({ exchangeName, @@ -751,10 +746,7 @@ const submit = function(exchangeName, routingKey, data, options, callback) { channelWrapper.publish(exchangeName, routingKey, data, options, err => { if (err) { - log().error( - { exchangeName, routingKey, data, options }, - 'Failed to submit a message to an exchange' - ); + log().error({ exchangeName, routingKey, data, options }, 'Failed to submit a message to an exchange'); return callback(err); } @@ -795,9 +787,7 @@ const _waitUntilIdle = function(maxWaitMillis, callback) { * output a certain amount of them to the logs for inspection. */ const forceContinueHandle = setTimeout(() => { - _dumpProcessingMessages( - 'Timed out ' + maxWaitMillis + 'ms while waiting for tasks to complete.' - ); + _dumpProcessingMessages('Timed out ' + maxWaitMillis + 'ms while waiting for tasks to complete.'); MQ.removeListener('idle', callback); return callback(); }, maxWaitMillis); @@ -880,6 +870,7 @@ const purgeAll = function(callback) { if (err) { return callback(err); } + if (_.isEmpty(toPurge)) { return callback(); } @@ -907,28 +898,24 @@ const _createRedeliveryQueue = function(callback) { // Declare an exchange with a queue whose sole purpose is to hold on to messages that were "redelivered". Such // situations include errors while processing or some kind of client error that resulted in the message not // being acknowledged - declareExchange( - MqConstants.REDELIVER_EXCHANGE_NAME, - MqConstants.REDELIVER_EXCHANGE_OPTIONS, - err => { + declareExchange(MqConstants.REDELIVER_EXCHANGE_NAME, MqConstants.REDELIVER_EXCHANGE_OPTIONS, err => { + if (err) { + return callback(err); + } + + declareQueue(MqConstants.REDELIVER_QUEUE_NAME, MqConstants.REDELIVER_QUEUE_OPTIONS, err => { if (err) { return callback(err); } - declareQueue(MqConstants.REDELIVER_QUEUE_NAME, MqConstants.REDELIVER_QUEUE_OPTIONS, err => { - if (err) { - return callback(err); - } - - return bindQueueToExchange( - MqConstants.REDELIVER_QUEUE_NAME, - MqConstants.REDELIVER_EXCHANGE_NAME, - MqConstants.REDELIVER_QUEUE_NAME, - callback - ); - }); - } - ); + return bindQueueToExchange( + MqConstants.REDELIVER_QUEUE_NAME, + MqConstants.REDELIVER_EXCHANGE_NAME, + MqConstants.REDELIVER_QUEUE_NAME, + callback + ); + }); + }); }; /** @@ -987,14 +974,11 @@ const _decrementProcessingTask = function(deliveryKey) { * @api private */ const _dumpProcessingMessages = function(logMessage) { - log().warn( - { messages: _.values(messagesInProcessing).slice(0, NUM_MESSAGES_TO_DUMP) }, - logMessage - ); + log().warn({ messages: _.values(messagesInProcessing).slice(0, NUM_MESSAGES_TO_DUMP) }, logMessage); }; -module.exports = { - emitter: MQ, +export { + MQ as emitter, rejectMessage, init, declareExchange, diff --git a/packages/oae-util/lib/oae.js b/packages/oae-util/lib/oae.js index a86a0f6036..7308435108 100644 --- a/packages/oae-util/lib/oae.js +++ b/packages/oae-util/lib/oae.js @@ -13,14 +13,14 @@ * permissions and limitations under the License. */ -const { logger } = require('oae-logger'); +import { logger } from 'oae-logger'; +import * as Modules from './modules'; +import OaeEmitter from './emitter'; +import * as Server from './server'; -const log = logger(); -const Modules = require('./modules'); -const OaeEmitter = require('./emitter'); -const Server = require('./server'); +import * as Shutdown from './internal/shutdown'; -const Shutdown = require('./internal/shutdown'); +const log = logger(); const SHUTDOWN_GRACE_TIME_MILLIS = 60000; const PRESHUTDOWN_DEFAULT_TIMEOUT_MILLIS = 15000; @@ -115,4 +115,4 @@ const registerPreShutdownHandler = function(name, maxTimeMillis, handler) { Shutdown.registerPreShutdownHandler(name, maxTimeMillis, handler); }; -module.exports = { globalAdminServer, tenantServer, tenantRouter, globalAdminRouter, init, registerPreShutdownHandler }; +export { globalAdminServer, tenantServer, tenantRouter, globalAdminRouter, init, registerPreShutdownHandler }; diff --git a/packages/oae-util/lib/pubsub.js b/packages/oae-util/lib/pubsub.js index cf644de73f..5b199602b5 100644 --- a/packages/oae-util/lib/pubsub.js +++ b/packages/oae-util/lib/pubsub.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const { EventEmitter } = require('oae-emitter'); -const Redis = require('./redis'); -const { Validator } = require('./validator'); +import { EventEmitter } from 'oae-emitter'; +import * as Redis from './redis'; +import { Validator } from './validator'; /*! * This module abstracts most of the redis publish/subscribe functions away. @@ -40,16 +40,19 @@ const init = function(config, callback) { if (err) { return callback(err); } + redisManager = client; Redis.createClient(config, (err, client) => { if (err) { return callback(err); } + redisSubscriber = client; Redis.createClient(config, (err, client) => { if (err) { return callback(err); } + redisPublisher = client; // Listen to all channels and emit them as events. @@ -81,11 +84,8 @@ const publish = function(channel, message, callback) { if (validator.hasErrors()) { return callback(validator.getFirstError()); } + redisPublisher.publish(channel, message, callback); }; -module.exports = { - publish, - init, - emitter -}; +export { publish, init, emitter }; diff --git a/packages/oae-util/lib/redis.js b/packages/oae-util/lib/redis.js index f7e83d563b..f6d6efd363 100644 --- a/packages/oae-util/lib/redis.js +++ b/packages/oae-util/lib/redis.js @@ -13,8 +13,10 @@ * permissions and limitations under the License. */ -const redis = require('redis'); -const log = require('oae-logger').logger('oae-redis'); +import redis from 'redis'; +import { logger } from 'oae-logger'; + +const log = logger('oae-redis'); let client = null; let isDown = false; @@ -133,9 +135,4 @@ const flush = function(callback) { } }; -module.exports = { - createClient, - getClient, - flush, - init -}; +export { createClient, getClient, flush, init }; diff --git a/packages/oae-util/lib/sanitization.js b/packages/oae-util/lib/sanitization.js index a541e24d11..5462bae071 100644 --- a/packages/oae-util/lib/sanitization.js +++ b/packages/oae-util/lib/sanitization.js @@ -13,7 +13,9 @@ * permissions and limitations under the License. */ -const encoder = require('node-esapi').encoder(); +import esapi from 'node-esapi'; + +const encoder = esapi.encoder(); /** * Encode the `value` parameter such that it is safe to be embedded into an HTML page. @@ -57,8 +59,4 @@ const encodeForURL = function(value) { return encoder.encodeForURL(value); }; -module.exports = { - encodeForHTML, - encodeForHTMLAttribute, - encodeForURL -}; +export { encodeForHTML, encodeForHTMLAttribute, encodeForURL }; diff --git a/packages/oae-util/lib/server.js b/packages/oae-util/lib/server.js index 549607b457..043763ecc6 100644 --- a/packages/oae-util/lib/server.js +++ b/packages/oae-util/lib/server.js @@ -13,18 +13,21 @@ * permissions and limitations under the License. */ -const http = require('http'); -const util = require('util'); -const _ = require('underscore'); -const bodyParser = require('body-parser'); -const express = require('express'); +import http from 'http'; +import util from 'util'; +import _ from 'underscore'; +import bodyParser from 'body-parser'; +import express from 'express'; -const log = require('oae-logger').logger('oae-server'); -const TelemetryAPI = require('oae-telemetry'); -const OaeEmitter = require('./emitter'); +import { logger } from 'oae-logger'; -const multipart = require('./middleware/multipart'); -const Shutdown = require('./internal/shutdown'); +import * as TelemetryAPI from 'oae-telemetry'; +import OaeEmitter from './emitter'; + +import multipart from './middleware/multipart'; +import * as Shutdown from './internal/shutdown'; + +const log = logger('oae-server'); // The main OAE config let config = null; @@ -360,10 +363,4 @@ const _isSameOrigin = function(req) { return true; }; -module.exports = { - setupServer, - setupRouter, - addSafePathPrefix, - postInitializeServer, - useHttps -}; +export { setupServer, setupRouter, addSafePathPrefix, postInitializeServer, useHttps }; diff --git a/packages/oae-util/lib/signature.js b/packages/oae-util/lib/signature.js index 2cd81315b8..a5e4b7d6af 100644 --- a/packages/oae-util/lib/signature.js +++ b/packages/oae-util/lib/signature.js @@ -13,12 +13,15 @@ * permissions and limitations under the License. */ -const crypto = require('crypto'); -const util = require('util'); -const _ = require('underscore'); +import crypto from 'crypto'; +import util from 'util'; +import _ from 'underscore'; -const log = require('oae-logger').logger('signature'); -const OaeUtil = require('oae-util/lib/util'); +import { logger } from 'oae-logger'; + +import * as OaeUtil from 'oae-util/lib/util'; + +const log = logger('signature'); let signKey = null; @@ -200,7 +203,7 @@ const _createResourceData = function(ctx, resourceId) { return { userId, resourceId }; }; -module.exports = { +export { init, sign, verify, diff --git a/packages/oae-util/lib/swagger.js b/packages/oae-util/lib/swagger.js index b95a19a915..0f95db6bd1 100644 --- a/packages/oae-util/lib/swagger.js +++ b/packages/oae-util/lib/swagger.js @@ -13,18 +13,21 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const util = require('util'); -const _ = require('underscore'); -const clone = require('clone'); -const readdirp = require('readdirp'); -const restjsdoc = require('restjsdoc'); - -const log = require('oae-logger').logger('oae-swagger'); -const OaeEmitter = require('oae-util/lib/emitter'); -const TenantsUtil = require('oae-tenants/lib/util'); -const { Validator } = require('oae-util/lib/validator'); -const SwaggerParamTypes = require('./swaggerParamTypes'); +import fs from 'fs'; +import util from 'util'; +import OaeEmitter from 'oae-util/lib/emitter'; + +import { logger } from 'oae-logger'; + +import _ from 'underscore'; +import clone from 'clone'; +import readdirp from 'readdirp'; +import * as restjsdoc from 'restjsdoc'; +import * as TenantsUtil from 'oae-tenants/lib/util'; +import { Validator } from 'oae-util/lib/validator'; +import * as SwaggerParamTypes from './swaggerParamTypes'; + +const log = logger('oae-swagger'); const Constants = { apiVersion: '0.1', @@ -505,11 +508,4 @@ const _unwrapSwaggerType = function(type) { return type.replace(/^List\[/, '').replace(/\]/, ''); }; -module.exports = { - Constants, - addModelsToResources, - documentModule, - register, - getResources, - getApi -}; +export { Constants, addModelsToResources, documentModule, register, getResources, getApi }; diff --git a/packages/oae-util/lib/swaggerParamTypes.js b/packages/oae-util/lib/swaggerParamTypes.js index 2d2b6899df..b5c1d839b9 100644 --- a/packages/oae-util/lib/swaggerParamTypes.js +++ b/packages/oae-util/lib/swaggerParamTypes.js @@ -26,15 +26,7 @@ * @return {QueryParameter} A swagger QueryParameter */ /* eslint-disable unicorn/filename-case */ -const query = function( - name, - description, - dataType, - required, - allowMultiple, - allowableValues, - defaultValue -) { +const query = function(name, description, dataType, required, allowMultiple, allowableValues, defaultValue) { return { paramType: 'query', name, @@ -135,10 +127,4 @@ const header = function(name, description, dataType, required) { }; }; -module.exports = { - query, - path, - body, - form, - header -}; +export { query, path, body, form, header }; diff --git a/packages/oae-util/lib/taskqueue.js b/packages/oae-util/lib/taskqueue.js index 0ed9a58e0b..14e472d46b 100644 --- a/packages/oae-util/lib/taskqueue.js +++ b/packages/oae-util/lib/taskqueue.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const MQ = require('./mq'); +import * as MQ from './mq'; /** * ## The Task Queue API. @@ -59,11 +59,7 @@ const Constants = { * @param {Function} callback Standard callback function */ const init = function(callback) { - MQ.declareExchange( - Constants.DEFAULT_TASK_EXCHANGE_NAME, - Constants.DEFAULT_TASK_EXCHANGE_OPTS, - callback - ); + MQ.declareExchange(Constants.DEFAULT_TASK_EXCHANGE_NAME, Constants.DEFAULT_TASK_EXCHANGE_OPTS, callback); }; /** @@ -94,20 +90,17 @@ const bind = function(taskQueueId, listener, options, callback) { } /* - * 2. Bind queue to the default exchange - * - * We use the `taskQueueId` for both the name as the queue and the routing key. - */ + * 2. Bind queue to the default exchange + * + * We use the `taskQueueId` for both the name as the queue and the routing key. + */ MQ.bindQueueToExchange(taskQueueId, Constants.DEFAULT_TASK_EXCHANGE_NAME, taskQueueId, err => { if (err) { return callback(err); } // 3. Subscribe to the queue - const subscribeOptions = _.defaults( - options.subscribe, - Constants.DEFAULT_TASK_QUEUE_SUBSCRIBE_OPTS - ); + const subscribeOptions = _.defaults(options.subscribe, Constants.DEFAULT_TASK_QUEUE_SUBSCRIBE_OPTS); MQ.subscribeQueue(taskQueueId, subscribeOptions, listener, callback); }); }); @@ -122,6 +115,7 @@ const _declareQueue = function(taskQueueId, queueOptions, callback) { if (MQ.isQueueDeclared(taskQueueId)) { return callback(); } + MQ.declareQueue(taskQueueId, queueOptions, callback); }; @@ -147,10 +141,4 @@ const submit = function(taskQueueId, taskData, callback) { MQ.submit(Constants.DEFAULT_TASK_EXCHANGE_NAME, taskQueueId, taskData, null, callback); }; -module.exports = { - Constants, - init, - bind, - unbind, - submit -}; +export { Constants, init, bind, unbind, submit }; diff --git a/packages/oae-util/lib/tempfile.js b/packages/oae-util/lib/tempfile.js index a188ccce90..3c60c81e44 100644 --- a/packages/oae-util/lib/tempfile.js +++ b/packages/oae-util/lib/tempfile.js @@ -13,11 +13,13 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const Path = require('path'); -const Temp = require('temp'); +import fs from 'fs'; +import Path from 'path'; +import Temp from 'temp'; -const log = require('oae-logger').logger('TempFile'); +import { logger } from 'oae-logger'; + +const log = logger('TempFile'); /** * Allows you to set the directory in which the temporary files will be generated. @@ -25,7 +27,7 @@ const log = require('oae-logger').logger('TempFile'); * @param {String} directory The base directory for temp files as generated by `createTempFile`. */ const init = function(directory) { - Temp.dir = directory; + Temp.dir = directory; }; /** @@ -35,47 +37,48 @@ const init = function(directory) { * @param {Number} size The filesize of this temporary file in bytes. */ const TempFile = function(path, size) { - const that = {}; - that.path = path; - that.size = size; - that.name = Path.basename(path); + const that = {}; + that.path = path; + that.size = size; + that.name = Path.basename(path); + + /** + * Remove the temporary file. + * + * @param {Function} callback Standard callback function + * @param {Object} callback.err An error that occurred, if any + */ + that.remove = function(callback) { + fs.unlink(that.path, err => { + if (err) { + log().error({ err }, 'Could not remove temporary file: %s', that.path); + return callback({ code: 500, msg: 'Could not remove temporary file ' + that.path }); + } - /** - * Remove the temporary file. - * - * @param {Function} callback Standard callback function - * @param {Object} callback.err An error that occurred, if any - */ - that.remove = function(callback) { - fs.unlink(that.path, (err) => { - if (err) { - log().error({err}, 'Could not remove temporary file: %s', that.path); - return callback({'code': 500, 'msg': 'Could not remove temporary file ' + that.path}); - } - callback(); - }); - }; + callback(); + }); + }; - /** - * Updates the metadata of a temp file. - * - * @param {Function} callback Standard callback function - * @param {Object} callback.err An error that occurred, if any - * @param {Object} callback.file Returns the updated temp file (a reference to it self.) - */ - that.update = function(callback) { - fs.stat(that.path, (err, stat) => { - if (err) { - log().error({err}, 'Could not stat the file: %s', that.path); - return callback({'code': 500, 'msg': err}); - } + /** + * Updates the metadata of a temp file. + * + * @param {Function} callback Standard callback function + * @param {Object} callback.err An error that occurred, if any + * @param {Object} callback.file Returns the updated temp file (a reference to it self.) + */ + that.update = function(callback) { + fs.stat(that.path, (err, stat) => { + if (err) { + log().error({ err }, 'Could not stat the file: %s', that.path); + return callback({ code: 500, msg: err }); + } - that.size = stat.size; - callback(null, that); - }); - }; + that.size = stat.size; + callback(null, that); + }); + }; - return that; + return that; }; /** @@ -87,15 +90,9 @@ const TempFile = function(path, size) { * @return {TempFile} */ const createTempFile = function(options) { - options = options || {}; - const tempPath = Temp.path(options); - return new TempFile(tempPath, 0); + options = options || {}; + const tempPath = Temp.path(options); + return new TempFile(tempPath, 0); }; - -module.exports = { -init , -TempFile , -createTempFile - -}; +export { init, TempFile, createTempFile }; diff --git a/packages/oae-util/lib/test/mq-util.js b/packages/oae-util/lib/test/mq-util.js index 9e19e146ef..b34bfcf65b 100644 --- a/packages/oae-util/lib/test/mq-util.js +++ b/packages/oae-util/lib/test/mq-util.js @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); +import _ from 'underscore'; -const Counter = require('oae-util/lib/counter'); -const MQ = require('oae-util/lib/mq'); +import Counter from 'oae-util/lib/counter'; +import * as MQ from 'oae-util/lib/mq'; // Track when counts for a particular type of task return to 0 const queueCounters = {}; @@ -88,6 +88,4 @@ const _decrement = function(name, count) { queueCounters[name].decr(count); }; -module.exports = { - whenTasksEmpty -}; +export { whenTasksEmpty }; diff --git a/packages/oae-util/lib/tz.js b/packages/oae-util/lib/tz.js index 917b3b7b51..870e911d18 100644 --- a/packages/oae-util/lib/tz.js +++ b/packages/oae-util/lib/tz.js @@ -13,20 +13,20 @@ * permissions and limitations under the License. */ -const fs = require('fs'); -const path = require('path'); -const _ = require('underscore'); -const tz = require('timezone-js'); -const railsTimezone = require('rails-timezone'); +import fs from 'fs'; +import path from 'path'; +import _ from 'underscore'; +import tz from 'timezone-js'; +import railsTimezone from 'rails-timezone'; -const RailsMappings = require('oae-util/timezones-rails'); +import RailsMappings from 'oae-util/timezones-rails'; tz.timezone.loadingScheme = tz.timezone.loadingSchemes.MANUAL_LOAD; tz.timezone.transport = function(opts) { return fs.readFileSync(opts.url, 'utf8'); }; + tz.timezone.loadZoneJSONData(path.join(__dirname, '/../timezones.json'), true); -module.exports.timezone = tz; /** * Given a ruby-on-rails supported timezone name, map it to a TZInfo identifier supported by OAE. @@ -180,9 +180,4 @@ const getZones = function() { return railsZones; }; -module.exports = { - getTimezoneFromRails, - getClosestSupportedTimezone, - getZones, - timezone: tz -}; +export { getTimezoneFromRails, tz as timezone, getClosestSupportedTimezone, getZones }; diff --git a/packages/oae-util/lib/util.js b/packages/oae-util/lib/util.js index 9854a61cd2..7183ea10f2 100644 --- a/packages/oae-util/lib/util.js +++ b/packages/oae-util/lib/util.js @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -const util = require('util'); -const _ = require('underscore'); +import util from 'util'; +import _ from 'underscore'; /// /////////////// // GLOBAL UTILS // @@ -23,7 +23,7 @@ const _ = require('underscore'); // Include some global utilities and extensions (e.g., underscore mixins). See the directory // `oae-util/lib/internal/globals` for all the global definitions // eslint-disable-next-line import/no-unassigned-import -require('./internal/globals'); +import * as globals from './internal/globals'; /// //////////////// // OAEUTIL UTILS // @@ -40,9 +40,11 @@ const castToBoolean = function(value) { if (value === 'true' || value === '1') { return true; } + if (value === 'false' || value === '0') { return false; } + return value; }; @@ -63,9 +65,11 @@ const getNumberParam = function(val, defaultVal, minimum, maximum) { if ((minimum || minimum === 0) && val < minimum) { val = minimum; } + if ((maximum || maximum === 0) && val > maximum) { val = maximum; } + return val; }; @@ -116,8 +120,8 @@ const isUnspecified = function(val) { * @param {...Object} args The arguments for the provided method. The final argument should always be the `callback` method that needs to be invoked if `isNecessary` is false. It can be the same callback method invoked if the method is executed. */ const invokeIfNecessary = function(...args) { - let isNecessary = args[0]; - let method = args[1]; + const isNecessary = args[0]; + const method = args[1]; if (!isNecessary) { return _.last(args)(); } @@ -146,18 +150,13 @@ const toArray = function(val) { if (!val) { return []; } + // Underscore doesn't wrap primitive values if (typeof val === 'number' || typeof val === 'string' || val instanceof Date) { return [val]; } + return _.toArray(val); }; -module.exports = { - castToBoolean, - getNumberParam, - isUnspecified, - invokeIfNecessary, - getNodeModulesDir, - toArray -}; +export { castToBoolean, getNumberParam, isUnspecified, invokeIfNecessary, getNodeModulesDir, toArray }; diff --git a/packages/oae-util/lib/validator.js b/packages/oae-util/lib/validator.js index 3aa4fa2687..63e5a8fef7 100644 --- a/packages/oae-util/lib/validator.js +++ b/packages/oae-util/lib/validator.js @@ -13,11 +13,13 @@ * permissions and limitations under the License. */ -const _ = require('underscore'); -const tz = require('oae-util/lib/tz'); +import _ from 'underscore'; +import * as tz from 'oae-util/lib/tz'; +import * as OAEUI from 'oae-ui'; -const { Validator } = require('validator'); -module.exports.Validator = Validator; +import { Validator } from 'validator'; + +export { Validator }; const HOST_REGEX = /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?(:\d+)?$/i; @@ -50,6 +52,7 @@ Validator.prototype.getErrors = function() { if (this._errors && this._errors.length > 0) { return this._errors; } + return null; }; @@ -61,6 +64,7 @@ Validator.prototype.getErrorCount = function() { if (this._errors) { return this._errors.length; } + return 0; }; @@ -80,6 +84,7 @@ Validator.prototype.getFirstError = function() { if (this._errors && this._errors.length > 0) { return this._errors[0]; } + return null; }; @@ -125,6 +130,7 @@ Validator.prototype.isLoggedInUser = function(ctx, tenantAlias) { } else if (tenantAlias && ctx.tenant().alias !== tenantAlias) { this.error(this.msg || 'The context is associated to an invalid tenant'); } + return this; }; @@ -170,6 +176,7 @@ Validator.prototype.isObject = function(obj) { if (!_.isObject(obj)) { this.error(this.msg || 'A non-object has been passed in'); } + return this; }; @@ -188,6 +195,7 @@ Validator.prototype.isArray = function(arr) { if (!_.isArray(arr)) { this.error(this.msg || 'A non-array has been passed in'); } + return this; }; @@ -207,6 +215,7 @@ Validator.prototype.isBoolean = function(val) { if (!isBoolean) { this.error(this.msg || 'A non-boolean has been passed in'); } + return this; }; @@ -228,6 +237,7 @@ Validator.prototype.isDefined = function(val) { if (!isDefined) { this.error(this.msg || 'An undefined value has been passed in'); } + return this; }; @@ -244,6 +254,7 @@ Validator.prototype.isString = function(val) { if (!_.isString(val)) { this.error(this.msg || 'A non-string has been passed in'); } + return this; }; @@ -261,6 +272,7 @@ Validator.prototype.isValidTimeZone = function() { if (!tz.timezone.timezone.zones[this.str] || this.str.indexOf('/') === -1) { this.error(this.msg || 'Invalid timezone'); } + return this; }; @@ -357,7 +369,7 @@ const _hasCountryCode = function(code) { if (!countriesByCode) { // Lazy initialize the country code array so as to not form an cross- // dependency on `oae-ui` - countriesByCode = _.chain(require('oae-ui').getIso3166CountryInfo().countries) + countriesByCode = _.chain(OAEUI.getIso3166CountryInfo().countries) .indexBy('code') .mapObject(() => { return true; diff --git a/packages/oae-util/tests/test-cassandra.js b/packages/oae-util/tests/test-cassandra.js index 9de2d36b29..61406b88f9 100644 --- a/packages/oae-util/tests/test-cassandra.js +++ b/packages/oae-util/tests/test-cassandra.js @@ -15,14 +15,16 @@ /* eslint-disable no-unused-vars */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; -const Cassandra = require('oae-util/lib/cassandra'); -const OaeUtil = require('oae-util/lib/util'); -const TestsUtil = require('oae-tests/lib/util'); +import * as Cassandra from 'oae-util/lib/cassandra'; +import * as OaeUtil from 'oae-util/lib/util'; +import * as TestsUtil from 'oae-tests/lib/util'; -const cassandraLog = require('oae-logger').logger('oae-cassandra'); +import { logger } from 'oae-logger'; + +const cassandraLog = logger('oae-cassandra'); describe('Utilities', () => { describe('Cassandra', () => { @@ -312,8 +314,8 @@ describe('Utilities', () => { let numInvoked = 0; /*! - * Verifies that the onEach is invoked only once and that only one row is returned - */ + * Verifies that the onEach is invoked only once and that only one row is returned + */ const _onEach = function(rows, done) { assert.strictEqual(++numInvoked, 1, 'Expected onEach to only be invoked once'); assert.ok(rows, 'Expected there to be rows provided to the onEach'); @@ -334,9 +336,9 @@ describe('Utilities', () => { numInvoked = 0; /*! - * Verifies that the onEach is invoked only once, that only one row is returned and it only contains - * the colOne column - */ + * Verifies that the onEach is invoked only once, that only one row is returned and it only contains + * the colOne column + */ const _onEach = function(rows, done) { assert.strictEqual(++numInvoked, 1, 'Expected onEach to only be invoked once'); assert.ok(rows, 'Expected a rows object to be specified'); @@ -390,9 +392,9 @@ describe('Utilities', () => { let allRows = {}; /*! - * Verifies that we receive exactly one row at a time, and aggregates them so we can inspect their - * data when finished. - */ + * Verifies that we receive exactly one row at a time, and aggregates them so we can inspect their + * data when finished. + */ const _onEach = function(rows, done) { numInvoked++; // Store the row so we can verify them all later @@ -422,9 +424,9 @@ describe('Utilities', () => { allRows = {}; /*! - * Verifies that the onEach is invoked with 5 rows at a time, and aggregates them so we can - * inspect their data when finished. - */ + * Verifies that the onEach is invoked with 5 rows at a time, and aggregates them so we can + * inspect their data when finished. + */ const _onEach = function(rows, done) { numInvoked++; // Record the rows so we can verify their contents at the end @@ -454,9 +456,9 @@ describe('Utilities', () => { allRows = {}; /*! - * Verifies that the onEach is called once with 7 rows, and then once with 3 rows, and aggregates - * them so we can inspect their data when finished. - */ + * Verifies that the onEach is called once with 7 rows, and then once with 3 rows, and aggregates + * them so we can inspect their data when finished. + */ const _onEach = function(rows, done) { numInvoked++; if (numInvoked === 1) { diff --git a/packages/oae-util/tests/test-cleaner.js b/packages/oae-util/tests/test-cleaner.js index 8b50fec0a5..28b9b3da44 100644 --- a/packages/oae-util/tests/test-cleaner.js +++ b/packages/oae-util/tests/test-cleaner.js @@ -13,19 +13,18 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const mkdirp = require('mkdirp'); -const _ = require('underscore'); -const shell = require('shelljs'); +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import mkdirp from 'mkdirp'; +import _ from 'underscore'; +import shell from 'shelljs'; -const Cleaner = require('oae-util/lib/cleaner'); +import * as Cleaner from 'oae-util/lib/cleaner'; describe('Content', () => { describe('Cleaner', () => { - let dir = - process.env.TMP || process.env.TMPDIR || process.env.TEMP || path.join(process.cwd(), 'tmp'); + let dir = process.env.TMP || process.env.TMPDIR || process.env.TEMP || path.join(process.cwd(), 'tmp'); dir = path.join(dir, 'oae', 'tests'); // We need to normalize as some OSes (like Mac OS X) return a path with double slashes. @@ -75,6 +74,7 @@ describe('Content', () => { }); } }; + Cleaner.emitter.on('cleaned', onCleaned); }); @@ -102,6 +102,7 @@ describe('Content', () => { }); } }; + Cleaner.emitter.on('cleaned', onCleaned); }); }); diff --git a/packages/oae-util/tests/test-globals.js b/packages/oae-util/tests/test-globals.js index 456689483b..219b710cc7 100644 --- a/packages/oae-util/tests/test-globals.js +++ b/packages/oae-util/tests/test-globals.js @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const _ = require('underscore'); +import assert from 'assert'; +import _ from 'underscore'; describe('Globals', () => { describe('Underscore', () => { diff --git a/packages/oae-util/tests/test-image.js b/packages/oae-util/tests/test-image.js index f7c46cc8d8..3be7e4248d 100644 --- a/packages/oae-util/tests/test-image.js +++ b/packages/oae-util/tests/test-image.js @@ -13,12 +13,12 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const Path = require('path'); -const gm = require('gm'); +import assert from 'assert'; +import fs from 'fs'; +import Path from 'path'; +import gm from 'gm'; -const ImageUtil = require('oae-util/lib/image'); +import * as ImageUtil from 'oae-util/lib/image'; /** * Most of the tests in this suite don't actually crop or resize anything. @@ -157,115 +157,98 @@ describe('Image', () => { * Simple validation checks. */ it('verify parameter validation', callback => { - ImageUtil.cropAndResize( - undefined, - generateArea(0, 0, 200, 200), - [generateSize(100, 100)], - (err, files) => { + ImageUtil.cropAndResize(undefined, generateArea(0, 0, 200, 200), [generateSize(100, 100)], (err, files) => { + assert.strictEqual(err.code, 400); + assert.ok(!files); + ImageUtil.cropAndResize('some/path', null, [generateSize(100, 100)], (err, files) => { assert.strictEqual(err.code, 400); assert.ok(!files); - ImageUtil.cropAndResize('some/path', null, [generateSize(100, 100)], (err, files) => { - assert.strictEqual(err.code, 400); - assert.ok(!files); - ImageUtil.cropAndResize( - 'some/path', - generateArea(-10, 0, 200, 200), - [generateSize(100, 100)], - (err, files) => { - assert.strictEqual(err.code, 400); - assert.ok(!files); - ImageUtil.cropAndResize( - 'some/path', - generateArea(0, -10, 200, 200), - [generateSize(100, 100)], - (err, files) => { - assert.strictEqual(err.code, 400); - assert.ok(!files); - ImageUtil.cropAndResize( - 'some/path', - generateArea(0, 0, -10, 200), - [generateSize(100, 100)], - (err, files) => { - assert.strictEqual(err.code, 400); - assert.ok(!files); - ImageUtil.cropAndResize( - 'some/path', - generateArea(-10, 0, 200, 200), - [generateSize(100, 100)], - (err, files) => { + ImageUtil.cropAndResize( + 'some/path', + generateArea(-10, 0, 200, 200), + [generateSize(100, 100)], + (err, files) => { + assert.strictEqual(err.code, 400); + assert.ok(!files); + ImageUtil.cropAndResize( + 'some/path', + generateArea(0, -10, 200, 200), + [generateSize(100, 100)], + (err, files) => { + assert.strictEqual(err.code, 400); + assert.ok(!files); + ImageUtil.cropAndResize( + 'some/path', + generateArea(0, 0, -10, 200), + [generateSize(100, 100)], + (err, files) => { + assert.strictEqual(err.code, 400); + assert.ok(!files); + ImageUtil.cropAndResize( + 'some/path', + generateArea(-10, 0, 200, 200), + [generateSize(100, 100)], + (err, files) => { + assert.strictEqual(err.code, 400); + assert.ok(!files); + ImageUtil.cropAndResize('some/path', generateArea(10, 0, 200, 200), null, (err, files) => { assert.strictEqual(err.code, 400); assert.ok(!files); - ImageUtil.cropAndResize( - 'some/path', - generateArea(10, 0, 200, 200), - null, - (err, files) => { - assert.strictEqual(err.code, 400); - assert.ok(!files); - ImageUtil.cropAndResize( - 'some/path', - generateArea(10, 0, 200, 200), - [], - (err, files) => { - assert.strictEqual(err.code, 400); - assert.ok(!files); - ImageUtil.cropAndResize( - 'some/path', - generateArea(10, 0, 200, 200), - [generateSize(-10, 10)], - (err, files) => { - assert.strictEqual(err.code, 400); - assert.ok(!files); - ImageUtil.cropAndResize( - 'some/path', - generateArea(10, 0, 200, 200), - [generateSize(10, -10)], - (err, files) => { - assert.strictEqual(err.code, 400); - assert.ok(!files); - // Sanity check. - const path = Path.resolve( - Path.join(__dirname, '/data/right.jpg') - ); - ImageUtil.cropAndResize( - path, - generateArea(10, 0, 10, 10), - [generateSize(20, 20)], - (err, files) => { - assert.ok(!err); - assert.ok(files); - assert.ok(files['20x20']); - assert.ok(files['20x20'].path); - assert.ok(fs.existsSync(files['20x20'].path)); - assert.ok(files['20x20'].name); - assert.ok(files['20x20'].size > 0); - gm(files['20x20'].path).size((err, size) => { - assert.ok(!err); - assert.strictEqual(size.width, 20); - assert.strictEqual(size.height, 20); - callback(); - }); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - } - ); - }); - } - ); + ImageUtil.cropAndResize('some/path', generateArea(10, 0, 200, 200), [], (err, files) => { + assert.strictEqual(err.code, 400); + assert.ok(!files); + ImageUtil.cropAndResize( + 'some/path', + generateArea(10, 0, 200, 200), + [generateSize(-10, 10)], + (err, files) => { + assert.strictEqual(err.code, 400); + assert.ok(!files); + ImageUtil.cropAndResize( + 'some/path', + generateArea(10, 0, 200, 200), + [generateSize(10, -10)], + (err, files) => { + assert.strictEqual(err.code, 400); + assert.ok(!files); + // Sanity check. + const path = Path.resolve(Path.join(__dirname, '/data/right.jpg')); + ImageUtil.cropAndResize( + path, + generateArea(10, 0, 10, 10), + [generateSize(20, 20)], + (err, files) => { + assert.ok(!err); + assert.ok(files); + assert.ok(files['20x20']); + assert.ok(files['20x20'].path); + assert.ok(fs.existsSync(files['20x20'].path)); + assert.ok(files['20x20'].name); + assert.ok(files['20x20'].size > 0); + gm(files['20x20'].path).size((err, size) => { + assert.ok(!err); + assert.strictEqual(size.width, 20); + assert.strictEqual(size.height, 20); + callback(); + }); + } + ); + } + ); + } + ); + }); + }); + } + ); + } + ); + } + ); + } + ); + }); + }); }); }); @@ -296,9 +279,7 @@ describe('Image', () => { * Test that verifies that images get properly converted to JPG */ it('verify images get properly converted to JPG', callback => { - const path = Path.resolve( - Path.join(__dirname, '/../../oae-preview-processor/tests/data/image.gif') - ); + const path = Path.resolve(Path.join(__dirname, '/../../oae-preview-processor/tests/data/image.gif')); ImageUtil.convertToJPG(path, (err, file) => { assert.ok(!err); assert.ok(file); diff --git a/packages/oae-util/tests/test-io.js b/packages/oae-util/tests/test-io.js index aca934c591..24416af0e6 100644 --- a/packages/oae-util/tests/test-io.js +++ b/packages/oae-util/tests/test-io.js @@ -13,11 +13,11 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; -const IO = require('oae-util/lib/io'); +import * as IO from 'oae-util/lib/io'; const datadir = path.join(__dirname, '/data/'); diff --git a/packages/oae-util/tests/test-locking.js b/packages/oae-util/tests/test-locking.js index cb5d516c94..58c54fc70b 100644 --- a/packages/oae-util/tests/test-locking.js +++ b/packages/oae-util/tests/test-locking.js @@ -13,9 +13,8 @@ * permissions and limitations under the License. */ -const assert = require('assert'); - -const Locking = require('oae-util/lib/locking'); +import assert from 'assert'; +import * as Locking from 'oae-util/lib/locking'; describe('Locking', () => { /** diff --git a/packages/oae-util/tests/test-mq.js b/packages/oae-util/tests/test-mq.js index 299d40833c..61d786562c 100644 --- a/packages/oae-util/tests/test-mq.js +++ b/packages/oae-util/tests/test-mq.js @@ -13,13 +13,13 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const util = require('util'); -const _ = require('underscore'); -const ShortId = require('shortid'); +import assert from 'assert'; +import util from 'util'; +import _ from 'underscore'; +import ShortId from 'shortid'; -const MQ = require('oae-util/lib/mq'); -const TaskQueue = require('oae-util/lib/taskqueue'); +import * as MQ from 'oae-util/lib/mq'; +import * as TaskQueue from 'oae-util/lib/taskqueue'; describe('MQ', () => { /** @@ -265,6 +265,7 @@ describe('MQ', () => { return callback(); }); }; + MQ.subscribeQueue(queueName, {}, listener, err => { assert.ok(!err); @@ -382,6 +383,7 @@ describe('MQ', () => { // Verify the message we receive is correct assert.strictEqual(msg.text, data.text); }; + MQ.subscribeQueue(queueName, {}, listener, err => { assert.ok(!err); @@ -395,7 +397,7 @@ describe('MQ', () => { // Submit one more message. If it ends up at our listener the test will fail MQ.submit(exchangeName, routingKey, data, () => { - return; + }); }); }); @@ -458,23 +460,19 @@ describe('MQ', () => { // Declare an exchange that acknowledges the message exchangeName = util.format('testExchange-%s', ShortId.generate()); - MQ.declareExchange( - exchangeName, - { durable: false, autoDelete: true, confirm: true }, - err => { - assert.ok(!err); + MQ.declareExchange(exchangeName, { durable: false, autoDelete: true, confirm: true }, err => { + assert.ok(!err); - let confirmCalled = 0; - MQ.submit(exchangeName, routingKey, data, null, err => { - assert.ok(!err); + let confirmCalled = 0; + MQ.submit(exchangeName, routingKey, data, null, err => { + assert.ok(!err); - // This should only be executed once - confirmCalled++; - assert.strictEqual(confirmCalled, 1); - return callback(); - }); - } - ); + // This should only be executed once + confirmCalled++; + assert.strictEqual(confirmCalled, 1); + return callback(); + }); + }); }); }); }); @@ -522,47 +520,44 @@ describe('MQ', () => { }); // When the raw message comes in, reject it so it gets redelivered - _bindPreHandleOnce( - queueName, - (_queueName, data, headers, deliveryInfo, message) => { - // Reject the message, indicating that we want it requeued and redelivered - MQ.rejectMessage(message, true, () => { - // Ensure that rabbitmq intercepts the redelivery of the rejected message and stuffs it in the redelivery queue - // for manual intervention - MQ.emitter.once('storedRedelivery', _queueName => { - // Here we make sure that the listener received the message the first time. But this does not - // ensure it doesn't receive it the second time. That is what the `assert.fail` is for in the - // listener - assert.strictEqual(handledMessages, 1); - assert.strictEqual(queueName, _queueName); - - // Make sure we can take the item off the redelivery queue - MQ.subscribeQueue( - 'oae-util-mq-redeliverqueue', - { prefetchCount: 1 }, - (data, listenerCallback) => { - assert.ok(data); - assert.ok(data.headers); - assert.strictEqual(data.deliveryInfo.queue, queueName); - assert.strictEqual(data.deliveryInfo.exchange, exchangeName); - assert.strictEqual(data.deliveryInfo.routingKey, routingKey); - assert.strictEqual(data.data.data, 'test'); - - // Don't accept any more messages on this queue - MQ.unsubscribeQueue('oae-util-mq-redeliverqueue', err => { - assert.ok(!err); - - // Acknowledge the redelivered message so it doesn't go in an infinite redelivery loop - listenerCallback(); - - return callback(); - }); - } - ); - }); + _bindPreHandleOnce(queueName, (_queueName, data, headers, deliveryInfo, message) => { + // Reject the message, indicating that we want it requeued and redelivered + MQ.rejectMessage(message, true, () => { + // Ensure that rabbitmq intercepts the redelivery of the rejected message and stuffs it in the redelivery queue + // for manual intervention + MQ.emitter.once('storedRedelivery', _queueName => { + // Here we make sure that the listener received the message the first time. But this does not + // ensure it doesn't receive it the second time. That is what the `assert.fail` is for in the + // listener + assert.strictEqual(handledMessages, 1); + assert.strictEqual(queueName, _queueName); + + // Make sure we can take the item off the redelivery queue + MQ.subscribeQueue( + 'oae-util-mq-redeliverqueue', + { prefetchCount: 1 }, + (data, listenerCallback) => { + assert.ok(data); + assert.ok(data.headers); + assert.strictEqual(data.deliveryInfo.queue, queueName); + assert.strictEqual(data.deliveryInfo.exchange, exchangeName); + assert.strictEqual(data.deliveryInfo.routingKey, routingKey); + assert.strictEqual(data.data.data, 'test'); + + // Don't accept any more messages on this queue + MQ.unsubscribeQueue('oae-util-mq-redeliverqueue', err => { + assert.ok(!err); + + // Acknowledge the redelivered message so it doesn't go in an infinite redelivery loop + listenerCallback(); + + return callback(); + }); + } + ); }); - } - ); + }); + }); }); }); }); @@ -582,10 +577,10 @@ describe('MQ', () => { */ const _bindPreHandleOnce = function(handlingQueueName, handler) { /*! - * Filters tasks by those on the expected queue, and immediately unbinds the - * handler so it only gets invoked once. The parameters are the MQ preHandle - * event parameters. - */ + * Filters tasks by those on the expected queue, and immediately unbinds the + * handler so it only gets invoked once. The parameters are the MQ preHandle + * event parameters. + */ const _handler = function(queueName, data, headers, deliveryInfo, message) { if (queueName !== handlingQueueName) { return; diff --git a/packages/oae-util/tests/test-pubsub.js b/packages/oae-util/tests/test-pubsub.js index a23c49ca7a..3eaa78c47c 100644 --- a/packages/oae-util/tests/test-pubsub.js +++ b/packages/oae-util/tests/test-pubsub.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const assert = require('assert'); +import assert from 'assert'; -const Pubsub = require('oae-util/lib/pubsub'); +import * as Pubsub from 'oae-util/lib/pubsub'; describe('Pubsub', () => { describe('#publish()', callback => { diff --git a/packages/oae-util/tests/test-sanitization.js b/packages/oae-util/tests/test-sanitization.js index c0bc49c3b2..932a0b7a2f 100644 --- a/packages/oae-util/tests/test-sanitization.js +++ b/packages/oae-util/tests/test-sanitization.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const assert = require('assert'); +import assert from 'assert'; -const Sanitization = require('oae-util/lib/sanitization'); +import * as Sanitization from 'oae-util/lib/sanitization'; describe('Sanitization', () => { /** diff --git a/packages/oae-util/tests/test-server.js b/packages/oae-util/tests/test-server.js index e387759939..06b3e39ed2 100644 --- a/packages/oae-util/tests/test-server.js +++ b/packages/oae-util/tests/test-server.js @@ -13,24 +13,24 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const _ = require('underscore'); +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import _ from 'underscore'; -const OaeServer = require('oae-util/lib/server'); -const PrincipalsTestUtil = require('oae-principals/lib/test/util'); -const RestAPI = require('oae-rest'); -const RestUtil = require('oae-rest/lib/util'); -const TestsUtil = require('oae-tests'); +import * as OaeServer from 'oae-util/lib/server'; +import * as PrincipalsTestUtil from 'oae-principals/lib/test/util'; +import * as RestAPI from 'oae-rest'; +import * as RestUtil from 'oae-rest/lib/util'; +import * as TestsUtil from 'oae-tests'; describe('OAE Server', () => { // Rest context for the cam admin let camAdminRestContext = null; /*! - * Function that will set up the user contexts - */ + * Function that will set up the user contexts + */ before(callback => { // Fill up Cam tenant admin rest context camAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.cam.host); @@ -39,8 +39,8 @@ describe('OAE Server', () => { describe('CSRF', () => { /*! - * Verifies CSRF validation with invalid hosts and safe paths - */ + * Verifies CSRF validation with invalid hosts and safe paths + */ it('verify CSRF validation with invalid hosts and safe paths', callback => { TestsUtil.generateTestUsers(camAdminRestContext, 1, (err, user) => { assert.ok(!err); @@ -178,7 +178,6 @@ describe('OAE Server', () => { // When setting up the tests, we configured the cookie name to a specific value. // Ensure that this value is used. This tests regressions in the cookie-session // middleware - const { config } = require('../../../config.js'); assert.ok(_.contains(cookieNames, TestsUtil.CONFIG_COOKIE_NAME)); // Rename the cookie to something else diff --git a/packages/oae-util/tests/test-signature.js b/packages/oae-util/tests/test-signature.js index 8aa9348823..813a4306f6 100644 --- a/packages/oae-util/tests/test-signature.js +++ b/packages/oae-util/tests/test-signature.js @@ -13,10 +13,9 @@ * permissions and limitations under the License. */ -const assert = require('assert'); +import assert from 'assert'; -const Signature = require('oae-util/lib/signature'); -const { Validator } = require('oae-util/lib/validator'); +import * as Signature from 'oae-util/lib/signature'; // Keep track of the node Date.now function since we will // override it at times in tests to mock a future date @@ -76,11 +75,7 @@ describe('Signature', () => { it('verify expiring signature cannot be verified with different data or expires timestamp', callback => { const signatureData = Signature.createExpiringSignature({ '0': 'zero', '1': 'one' }); assert.ok( - !Signature.verifyExpiringSignature( - { '0': 'one', '1': 'zero' }, - signatureData.expires, - signatureData.signature - ) + !Signature.verifyExpiringSignature({ '0': 'one', '1': 'zero' }, signatureData.expires, signatureData.signature) ); assert.ok( !Signature.verifyExpiringSignature( @@ -90,11 +85,7 @@ describe('Signature', () => { ) ); assert.ok( - Signature.verifyExpiringSignature( - { '0': 'zero', '1': 'one' }, - signatureData.expires, - signatureData.signature - ) + Signature.verifyExpiringSignature({ '0': 'zero', '1': 'one' }, signatureData.expires, signatureData.signature) ); return callback(); }); @@ -115,17 +106,15 @@ describe('Signature', () => { Date.now = function() { return now + 5 * 1000; }; - assert.ok( - Signature.verifyExpiringSignature(data, signatureData.expires, signatureData.signature) - ); + + assert.ok(Signature.verifyExpiringSignature(data, signatureData.expires, signatureData.signature)); // Ensure the signature is never valid 12 seconds from now Date.now = function() { return now + 12 * 1000; }; - assert.ok( - !Signature.verifyExpiringSignature(data, signatureData.expires, signatureData.signature) - ); + + assert.ok(!Signature.verifyExpiringSignature(data, signatureData.expires, signatureData.signature)); return callback(); }); @@ -140,12 +129,14 @@ describe('Signature', () => { Date.now = function() { return Date.UTC(2013, 0, 25, 11, 39, 46); }; + const signatureDataFirst = Signature.createExpiringSignature(data, 15 * 60, 15 * 60); // Increase the time by 5min and create a new signature with the same data Date.now = function() { return Date.UTC(2013, 0, 25, 11, 44, 46); }; + const signatureDataSecond = Signature.createExpiringSignature(data, 15 * 60, 15 * 60); // Ensure the signature from now is the same as the signature from 5m ago @@ -156,6 +147,7 @@ describe('Signature', () => { Date.now = function() { return Date.UTC(2013, 0, 25, 11, 55, 46); }; + const signatureDataThird = Signature.createExpiringSignature(data, 15 * 60, 15 * 60); assert.notStrictEqual(signatureDataSecond.expires, signatureDataThird.expires); assert.notStrictEqual(signatureDataSecond.signature, signatureDataThird.signature); diff --git a/packages/oae-util/tests/test-swagger.js b/packages/oae-util/tests/test-swagger.js index 12bb5499a4..61a5b698af 100644 --- a/packages/oae-util/tests/test-swagger.js +++ b/packages/oae-util/tests/test-swagger.js @@ -13,24 +13,21 @@ * permissions and limitations under the License. */ -const assert = require('assert'); -const util = require('util'); -const path = require('path'); -const _ = require('underscore'); +import assert from 'assert'; +import util from 'util'; +import path from 'path'; +import _ from 'underscore'; -const RestAPI = require('oae-rest'); -const { RestContext } = require('oae-rest/lib/model'); -const TestsUtil = require('oae-tests'); -const Swagger = require('../lib/swagger'); +import * as RestAPI from 'oae-rest'; +import * as TestsUtil from 'oae-tests'; +import * as Swagger from '../lib/swagger'; describe('Swagger', () => { let anonymousRestContext = null; let globalAdminRestContext = null; before(callback => { - anonymousRestContext = TestsUtil.createTenantRestContext( - global.oaeTests.tenants.localhost.host - ); + anonymousRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.localhost.host); globalAdminRestContext = TestsUtil.createGlobalAdminRestContext(); // Register the test doc Swagger.register(path.join(__dirname, '/data/restjsdoc.js'), () => { @@ -52,10 +49,7 @@ describe('Swagger', () => { _.each(resources.apis, api => { assert.ok(_.isString(api.path)); }); - assert.ok( - _.findWhere(resources.apis, { path: '/test' }), - 'There should be a resource for the "/test" apis' - ); + assert.ok(_.findWhere(resources.apis, { path: '/test' }), 'There should be a resource for the "/test" apis'); return callback(); }); }); @@ -88,10 +82,7 @@ describe('Swagger', () => { assert.ok(_.isObject(model.properties), 'Model properties must be an Object'); _.each(model.required, id => { assert.ok(_.isString(id), 'Required property ids must be Strings'); - assert.ok( - model.properties[id], - util.format('Required property "%s" is not defined', id) - ); + assert.ok(model.properties[id], util.format('Required property "%s" is not defined', id)); }); _.each(model.properties, property => { assert.ok(_.isString(property.type)); @@ -114,10 +105,7 @@ describe('Swagger', () => { // Complex type, make sure there's a model for it assert.ok( data.models[property.items.$ref], - util.format( - 'Array item $ref "%s" is not defined in models', - property.items.$ref - ) + util.format('Array item $ref "%s" is not defined in models', property.items.$ref) ); } } else if (!_.contains(Swagger.Constants.primitives, property.type)) { @@ -141,10 +129,7 @@ describe('Swagger', () => { const verbs = ['GET', 'POST', 'PUT', 'DELETE']; assert.ok( _.contains(verbs, operation.method), - util.format( - 'Operation method "%s" is not one of "GET", "POST", "PUT", or "DELETE"', - operation.method - ) + util.format('Operation method "%s" is not one of "GET", "POST", "PUT", or "DELETE"', operation.method) ); assert.ok(_.isString(operation.nickname), 'Operation nickname must be a String'); assert.ok( @@ -152,13 +137,8 @@ describe('Swagger', () => { util.format('Operation nickname "%s" cannot contain spaces', operation.nickname) ); assert.ok(_.isString(operation.summary), 'Operation summary must be a String'); - assert.ok( - _.isString(operation.responseClass), - 'Operation responseClass must be a String' - ); - const responseClass = operation.responseClass - .replace(/^List\[/, '') - .replace(/\]/, ''); + assert.ok(_.isString(operation.responseClass), 'Operation responseClass must be a String'); + const responseClass = operation.responseClass.replace(/^List\[/, '').replace(/\]/, ''); if ( !_.contains(Swagger.Constants.primitives, responseClass) && responseClass !== 'void' && @@ -173,10 +153,7 @@ describe('Swagger', () => { assert.ok(_.isArray(operation.parameters), 'Operation parameters must be an Array'); _.each(operation.parameters, parameter => { assert.ok(_.isString(parameter.name), 'Parameter name must be a String'); - assert.ok( - _.isString(parameter.description), - 'Parameter description must be a String' - ); + assert.ok(_.isString(parameter.description), 'Parameter description must be a String'); const dataType = parameter.dataType.replace(/^List\[/, '').replace(/\]/, ''); if (!_.contains(Swagger.Constants.primitives, dataType) && dataType !== 'File') { assert.ok( @@ -184,14 +161,9 @@ describe('Swagger', () => { util.format('Parameter dataType "%s" is undefined in models', dataType) ); } - assert.ok( - _.isBoolean(parameter.required), - 'Parameter required must be a Boolean' - ); - assert.ok( - _.isBoolean(parameter.allowMultiple), - 'Parameter allowMultiple must be a Boolean' - ); + + assert.ok(_.isBoolean(parameter.required), 'Parameter required must be a Boolean'); + assert.ok(_.isBoolean(parameter.allowMultiple), 'Parameter allowMultiple must be a Boolean'); const paramTypes = ['body', 'path', 'query', 'form', 'header']; assert.ok( _.contains(paramTypes, parameter.paramType), @@ -210,11 +182,7 @@ describe('Swagger', () => { if (_.contains(['path', 'query', 'header'], parameter.paramType)) { assert.ok( _.contains(Swagger.Constants.primitives, parameter.dataType), - util.format( - '%s parameter "%s" must be of a primitive type', - parameter.paramType, - parameter.name - ) + util.format('%s parameter "%s" must be of a primitive type', parameter.paramType, parameter.name) ); } }); @@ -272,10 +240,7 @@ describe('Swagger', () => { assert.strictEqual(params.var4.allowMultiple, false); assert.strictEqual(params.var4.paramType, 'query'); // Verify a query parameter that can appear multiple times - assert.strictEqual( - params.var5.description, - 'A query parameter that can appear multiple times' - ); + assert.strictEqual(params.var5.description, 'A query parameter that can appear multiple times'); assert.strictEqual(params.var5.dataType, 'string'); assert.strictEqual(params.var5.required, false); assert.strictEqual(params.var5.allowMultiple, true); @@ -312,14 +277,8 @@ describe('Swagger', () => { // Verify the responseMessages const responseMessages = _.indexBy(operation.responseMessages, 'code'); - assert.strictEqual( - responseMessages['404'].message, - 'Why this endpoint would send a 404' - ); - assert.strictEqual( - responseMessages['302'].message, - 'Why this endpoint would redirect' - ); + assert.strictEqual(responseMessages['404'].message, 'Why this endpoint would send a 404'); + assert.strictEqual(responseMessages['302'].message, 'Why this endpoint would redirect'); // Verify the `Test` model assert.strictEqual(_.size(data.models), 3); @@ -331,10 +290,7 @@ describe('Swagger', () => { assert.strictEqual(data.models.Test.properties.test.type, 'string'); assert.strictEqual(data.models.Test.properties.test.description, 'A property'); assert.strictEqual(data.models.Test.properties.test2.type, 'array'); - assert.strictEqual( - data.models.Test.properties.test2.description, - 'Array of Test2s' - ); + assert.strictEqual(data.models.Test.properties.test2.description, 'Array of Test2s'); assert.strictEqual(data.models.Test.properties.test2.items.$ref, 'Test2'); // Verify the `Test2` model @@ -347,10 +303,7 @@ describe('Swagger', () => { assert.strictEqual(data.models.Test2.properties.test.description, 'A property'); assert.strictEqual(data.models.Test2.properties.test.items.type, 'string'); assert.strictEqual(data.models.Test2.properties.num.type, 'number'); - assert.strictEqual( - data.models.Test2.properties.num.description, - 'A numeric property' - ); + assert.strictEqual(data.models.Test2.properties.num.description, 'A numeric property'); assert.strictEqual(data.models.Test2.properties.test3.type, 'Test3'); // Verify the `Test3` model diff --git a/packages/oae-util/tests/test-taskqueue.js b/packages/oae-util/tests/test-taskqueue.js index 34f5c868d4..fe653afa58 100644 --- a/packages/oae-util/tests/test-taskqueue.js +++ b/packages/oae-util/tests/test-taskqueue.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const assert = require('assert'); +import assert from 'assert'; -const TaskQueue = require('oae-util/lib/taskqueue'); +import * as TaskQueue from 'oae-util/lib/taskqueue'; describe('TaskQueue', () => { describe('#bind()', () => { diff --git a/packages/oae-util/tests/test-tz.js b/packages/oae-util/tests/test-tz.js index 95c1c50f17..8cccd12adf 100644 --- a/packages/oae-util/tests/test-tz.js +++ b/packages/oae-util/tests/test-tz.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const assert = require('assert'); +import assert from 'assert'; -const tz = require('oae-util/lib/tz'); +import * as tz from 'oae-util/lib/tz'; describe('TZ', () => { describe('#getTimeZoneFromRails()', () => { @@ -24,10 +24,7 @@ describe('TZ', () => { */ it('verify proper rails conversion', () => { assert.strictEqual(tz.getTimezoneFromRails('Brussels'), 'Europe/Brussels'); - assert.strictEqual( - tz.getTimezoneFromRails('Pacific Time (US & Canada)'), - 'America/Los_Angeles' - ); + assert.strictEqual(tz.getTimezoneFromRails('Pacific Time (US & Canada)'), 'America/Los_Angeles'); }); }); diff --git a/packages/oae-util/tests/test-util.js b/packages/oae-util/tests/test-util.js index 995d091557..0840e7e21f 100644 --- a/packages/oae-util/tests/test-util.js +++ b/packages/oae-util/tests/test-util.js @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -const assert = require('assert'); +import assert from 'assert'; -const OaeUtil = require('oae-util/lib/util'); +import * as OaeUtil from 'oae-util/lib/util'; describe('OAE Util', () => { describe('#getNumberParam', () => { @@ -78,12 +78,12 @@ describe('OAE Util', () => { describe('#invokeIfNecessary', () => { /*! - * The function to use as the invokeIfNecessary method so we can determine whether - * or not it was invoked - * - * @param {Object} toReturn The value to return in the callback - * @param {Function} callback Standard callback function - */ + * The function to use as the invokeIfNecessary method so we can determine whether + * or not it was invoked + * + * @param {Object} toReturn The value to return in the callback + * @param {Function} callback Standard callback function + */ const _toInvoke = function(toReturn, callback) { return callback(toReturn); }; diff --git a/packages/oae-util/tests/test-validator.js b/packages/oae-util/tests/test-validator.js index fdc627915f..3f617ac29b 100644 --- a/packages/oae-util/tests/test-validator.js +++ b/packages/oae-util/tests/test-validator.js @@ -13,14 +13,14 @@ * permissions and limitations under the License. */ -const assert = require('assert'); +import assert from 'assert'; -const { Context } = require('oae-context'); -const { Tenant } = require('oae-tenants/lib/model'); -const TestsUtil = require('oae-tests/lib/util'); -const { User } = require('oae-principals/lib/model.user'); +import { Context } from 'oae-context'; +import { Tenant } from 'oae-tenants/lib/model'; +import { User } from 'oae-principals/lib/model.user'; +import { Validator } from 'oae-util/lib/validator'; -const { Validator } = require('oae-util/lib/validator'); +import * as TestsUtil from 'oae-tests/lib/util'; describe('Utilities', () => { describe('Validator', () => { @@ -212,9 +212,7 @@ describe('Utilities', () => { validator.check('https://oae-widgets.oaeproject.org/sdk').isUrl(); validator.check('http://support.google.com/docs/bin/answer.py?hl=en&answer=66343').isUrl(); validator.check('http://www.w3.org/2004/02/skos/core#broader').isUrl(); - validator - .check('https://wordpress.org/support/topic/plugin-addthis-odd-url-string?replies=5') - .isUrl(); + validator.check('https://wordpress.org/support/topic/plugin-addthis-odd-url-string?replies=5').isUrl(); assert.ok(!validator.hasErrors()); assert.ok(!validator.getErrors()); assert.strictEqual(validator.getErrorCount(), 0); @@ -224,11 +222,7 @@ describe('Utilities', () => { validator.check('').isUrl(); validator.check('String').isUrl(); validator.check('www.example.com').isUrl(); - validator - .check( - 'https://twimg0-a.akamaihd.net/profile_images/300425859/ls_1278_Nicolaas-website.jpg' - ) - .isUrl(); + validator.check('https://twimg0-a.akamaihd.net/profile_images/300425859/ls_1278_Nicolaas-website.jpg').isUrl(); assert.ok(validator.hasErrors()); assert.strictEqual(validator.getErrors().length, 2); assert.strictEqual(validator.getErrorCount(), 2); diff --git a/packages/restjsdoc b/packages/restjsdoc index 31eaaa12fe..1f736ab6d0 160000 --- a/packages/restjsdoc +++ b/packages/restjsdoc @@ -1 +1 @@ -Subproject commit 31eaaa12fed01279be8ba068a68343598ea1e07f +Subproject commit 1f736ab6d0d69c6772331559939fccaa0b1ee6db From 4b25fb31b3fb6162fd5c88f944d87db4f767fdb8 Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Fri, 19 Apr 2019 14:31:29 +0100 Subject: [PATCH 18/21] build: small fixes to dependencies --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2ef6466f7d..d1f5d2f180 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "dox": "^0.9.0", "elasticsearchclient": "^0.5.3", "ent": "^2.2.0", - "esm": "^3.2.20", + "esm": "3.2.20", "ethercalc-client": "https://github.com/oaeproject/ethercalc-client.git", "etherpad-lite-client": "^0.8.0", "expand-url": "^0.1.3", @@ -59,6 +59,7 @@ "mobile-detect": "^1.3.7", "multiparty": "^4.1.3", "node-esapi": "^0.0.1", + "node-fetch": "^2.3.0", "nodegit": "^0.24.1", "nodemailer": "2.4.1", "nodemailer-html-to-text": "^3.0.0", @@ -129,7 +130,6 @@ "timezone-js": "^0.4.13", "tough-cookie": "3.0.1", "underscore": "^1.8.3", - "uservoice-sso": "^0.1.0", "validator": "1.1.3", "watch": "^1.0.2", "xml2js": "^0.4.19", From 9f513c361f956f1afa2cb2c296c39dff99b35d1f Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Fri, 19 Apr 2019 14:31:54 +0100 Subject: [PATCH 19/21] chore: update puppeteer version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d1f5d2f180..2f1c8b22fc 100644 --- a/package.json +++ b/package.json @@ -147,7 +147,7 @@ "nyc": "latest", "oauth": "^0.9.15", "prettier": "latest", - "puppeteer": "1.4.0", + "puppeteer": "1.11.0", "repl-promised": "^0.1.0", "shelljs": "^0.8.3", "sockjs-client-ws": "^0.1.0", From ab784dcbbd1479c41e0a80b3d55393efc2b42461 Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Fri, 19 Apr 2019 17:35:51 +0100 Subject: [PATCH 20/21] chore: update package.json lock file --- package-lock.json | 3835 ++++++++++++++++++++++++--------------------- 1 file changed, 2036 insertions(+), 1799 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9fdea740e3..5b91569cd7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,14 +24,6 @@ "lodash": "^4.17.11", "source-map": "^0.5.0", "trim-right": "^1.0.1" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } } }, "@babel/helper-function-name": { @@ -112,9 +104,9 @@ } }, "@babel/parser": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.2.tgz", - "integrity": "sha512-9fJTDipQFvlfSVdD/JBtkiY0br9BtfvW2R8wo6CX/Ej2eMuV0gWPk1M67Mt3eggQvBqYW1FCEk8BN7WvGm/g5g==", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.3.tgz", + "integrity": "sha512-gxpEUhTS1sGA63EGQGuA+WESPR/6tz6ng7tSHFCmaTJK/cGK8y37cBTspX+U2xCAue2IQVvF6Z0oigmjwD8YGQ==", "dev": true }, "@babel/template": { @@ -129,16 +121,16 @@ } }, "@babel/traverse": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.0.tgz", - "integrity": "sha512-/DtIHKfyg2bBKnIN+BItaIlEg5pjAnzHOIQe5w+rHAw/rg9g0V7T4rqPX8BJPfW11kt3koyjAnTNwCzb28Y1PA==", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.3.tgz", + "integrity": "sha512-HmA01qrtaCwwJWpSKpA948cBvU5BrmviAief/b3AVw936DtcdsTexlbyzNuDnthwhOQ37xshn7hvQaEQk7ISYQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "@babel/generator": "^7.4.0", "@babel/helper-function-name": "^7.1.0", "@babel/helper-split-export-declaration": "^7.4.0", - "@babel/parser": "^7.4.0", + "@babel/parser": "^7.4.3", "@babel/types": "^7.4.0", "debug": "^4.1.0", "globals": "^11.1.0", @@ -198,9 +190,9 @@ "dev": true }, "@snyk/dep-graph": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@snyk/dep-graph/-/dep-graph-1.4.0.tgz", - "integrity": "sha512-dz4Fo4L9sN0Rt8hMe+zMYZ4mAiljtyIeWvujLyCxhIT5iHSlceUYlba5TDsOFuKyuZKkuhVjORWY1oFEuRzCcA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@snyk/dep-graph/-/dep-graph-1.4.1.tgz", + "integrity": "sha512-7L096NNuNggcSjyOlITaU17n0dz0J4K4WpIHvatP4K0kIbhxolil1QbJF/+xKMRpW6OuaXILiP0hp7szhkEIzQ==", "requires": { "graphlib": "^2.1.5", "lodash": "^4", @@ -246,9 +238,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.1.tgz", - "integrity": "sha512-QgbIMRU1EVRry5cIu1ORCQP4flSYqLM1lS5LYyGWfKnFT3E58f0gKto7BR13clBFVrVZ0G0rbLZ1hUpSkgQQOA==", + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.2.tgz", + "integrity": "sha512-qgc8tjnDrc789rAQed8NoiFLV5VGcItA4yWNFphqGU0RcuuQngD00g3LHhWIK3HQ2XeDgVCmlNPDlqi3fWBHnQ==", "requires": { "@types/node": "*", "@types/range-parser": "*" @@ -286,9 +278,9 @@ "dev": true }, "@types/node": { - "version": "11.9.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.4.tgz", - "integrity": "sha512-Zl8dGvAcEmadgs1tmSPcvwzO1YRsz38bVJQvH1RvRqSR9/5n61Q1ktcDL0ht3FXWR+ZpVmXVwN1LuH4Ax23NsA==" + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.0.tgz", + "integrity": "sha512-rx29MMkRdVmzunmiA4lzBYJNnXsW/PhG4kMBy2ATsYaDjGGR75dCFEVVROKpNwlVdcUX3xxlghKQOeDPBJobng==" }, "@types/passport": { "version": "1.0.0", @@ -333,12 +325,19 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.0.7.tgz", + "integrity": "sha1-W1AftPBwQwmWTM2wSBclQSCNqxo=", "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" + "mime-types": "~1.0.0", + "negotiator": "0.4.7" + }, + "dependencies": { + "mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=" + } } }, "acorn": { @@ -366,37 +365,6 @@ "concat-stream": "^2.0.0", "exit-on-epipe": "^1.0.1", "printj": "^1.2.1" - }, - "dependencies": { - "concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "readable-stream": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.2.0.tgz", - "integrity": "sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "string_decoder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", - "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "after": { @@ -413,9 +381,9 @@ } }, "ajv": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", - "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -434,9 +402,9 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, "amqp-connection-manager": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/amqp-connection-manager/-/amqp-connection-manager-2.3.0.tgz", - "integrity": "sha512-DvebklFknBkareuf3wxE9X1Eo7l0UK1MgeO9m4B2T/h0OvzLRYsXTtQ8OrkXfgkg98FgKRRR9Nyz9+86aJFEaQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/amqp-connection-manager/-/amqp-connection-manager-2.3.1.tgz", + "integrity": "sha512-5uld2vHfUZWfTjYhCsmWhrpNsESUxfq0K1RllUUavngULgwxLZcsm0PjhhwqNwEXRhkK1uDMS/rzjUGCZBVYaA==", "requires": { "promise-breaker": "^4.1.2" } @@ -460,6 +428,35 @@ "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", "requires": { "string-width": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "ansi-colors": { @@ -469,9 +466,12 @@ "dev": true }, "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.1.0.tgz", + "integrity": "sha512-2VY/iCUZTDLD/qxptS3Zn3c6k2MeIbYqjRXqM8T5oC7N2mMjh3xIU3oYru6cHGbldFa9h5i8N0fP65UaUqrMWA==", + "requires": { + "type-fest": "^0.3.0" + } }, "ansi-regex": { "version": "2.1.1", @@ -538,11 +538,6 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -567,6 +562,11 @@ } } }, + "arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==" + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -674,9 +674,9 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, "ast-types": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.12.2.tgz", - "integrity": "sha512-8c83xDLJM/dLDyXNLiR6afRRm4dPKN6KAnKqytRK3DBJul9lA+atxdQkNDkSVPdTqea5HiRq3lnnOIZ0MBpvdg==" + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.12.3.tgz", + "integrity": "sha512-wJUcAfrdW+IgDoMGNz5MmcvahKgB7BwIbLupdKVVHxHNYt+HVR2k35swdYNv9aZpF8nvlkjbnkp2rrNwxGckZA==" }, "astral-regex": { "version": "1.0.0", @@ -693,9 +693,9 @@ } }, "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.2.tgz", + "integrity": "sha512-6xrbvN0MOBKSJDdonmSSz2OwFSgxRaVtBDes26mj9KIGtDo+g9xosFRSC+i1gQh2oAN/tQ62AI/pGZGQjVOiRg==", "dev": true }, "async-limiter": { @@ -822,6 +822,11 @@ "pascalcase": "^0.1.1" }, "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, "define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", @@ -874,13 +879,9 @@ "integrity": "sha1-As4P3u4M709ACA4ec+g08LG/zj8=" }, "base64url": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-1.0.6.tgz", - "integrity": "sha1-1k03XWinxkDZEuI1jRcNylu1RoE=", - "requires": { - "concat-stream": "~1.4.7", - "meow": "~2.0.0" - } + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" }, "basic-auth": { "version": "1.0.0", @@ -924,9 +925,9 @@ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" }, "binary-extensions": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.0.tgz", - "integrity": "sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", "dev": true }, "bindings": { @@ -983,9 +984,9 @@ } }, "bluebird": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", - "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", + "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==" }, "body-parser": { "version": "1.18.3", @@ -1031,6 +1032,11 @@ "widest-line": "^2.0.0" }, "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -1054,6 +1060,28 @@ "supports-color": "^5.3.0" } }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1190,6 +1218,13 @@ "to-object-path": "^0.3.0", "union-value": "^1.0.0", "unset-value": "^1.0.0" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + } } }, "call-me-maybe": { @@ -1204,9 +1239,9 @@ "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" }, "callsites": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", - "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, "camelcase": { @@ -1253,13 +1288,13 @@ "resolved": "https://registry.npmjs.org/cfb/-/cfb-0.11.1.tgz", "integrity": "sha1-qW248nKmw/uZ27sj70EiP0i+Hqc=", "requires": { - "commander": "^2.19.0" + "commander": "^2.20.0" }, "dependencies": { "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" } } }, @@ -1295,9 +1330,9 @@ "integrity": "sha512-g9YLQVHVZS/3F+zIicfB58vjcxopvYQRp7xHzvyDFDhXH1aRZI/JhwSAO0X5qYiQluoGnaNAU6wByD2KTxJN1A==" }, "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "check-error": { "version": "1.0.2", @@ -1306,12 +1341,12 @@ "dev": true }, "cheerio": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", - "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", "requires": { "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", + "dom-serializer": "~0.1.1", "entities": "~1.1.1", "htmlparser2": "^3.9.1", "lodash": "^4.15.0", @@ -1319,9 +1354,9 @@ } }, "chokidar": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.1.tgz", - "integrity": "sha512-gfw3p2oQV2wEt+8VuMlNsPjCxDxvvgnm/kz+uATu805mWVF8IJN7uz9DN7iBz+RMJISmiVbCOBFs9qBGMjtPfQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz", + "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -1335,7 +1370,7 @@ "normalize-path": "^3.0.0", "path-is-absolute": "^1.0.0", "readdirp": "^2.2.1", - "upath": "^1.1.0" + "upath": "^1.1.1" } }, "chownr": { @@ -1429,26 +1464,6 @@ "string-width": "^1.0.1", "strip-ansi": "^3.0.1", "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } } }, "clone": { @@ -1560,10 +1575,10 @@ "sshpk": "^1.7.0" } }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true }, "request": { @@ -1612,12 +1627,6 @@ "requires": { "safe-buffer": "^5.0.1" } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true } } }, @@ -1697,9 +1706,9 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colors": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", - "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=" + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" }, "combined-stream": { "version": "0.0.7", @@ -1729,9 +1738,9 @@ "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" }, "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz", + "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=" }, "component-inherit": { "version": "0.0.3", @@ -1756,15 +1765,6 @@ "vary": "~1.0.0" }, "dependencies": { - "accepts": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.0.7.tgz", - "integrity": "sha1-W1AftPBwQwmWTM2wSBclQSCNqxo=", - "requires": { - "mime-types": "~1.0.0", - "negotiator": "0.4.7" - } - }, "bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", @@ -1778,21 +1778,11 @@ "ms": "0.6.2" } }, - "mime-types": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", - "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=" - }, "ms": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=" }, - "negotiator": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.7.tgz", - "integrity": "sha1-pBYPcXfsgGc4Yx0NMFIyXaQqvcg=" - }, "vary": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.1.tgz", @@ -1806,13 +1796,34 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", - "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", "requires": { - "inherits": "~2.0.1", - "readable-stream": "~1.1.9", - "typedarray": "~0.0.5" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", + "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "configstore": { @@ -1840,6 +1851,11 @@ "requires": { "pify": "^3.0.0" } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" } } }, @@ -1993,13 +2009,6 @@ "requires": { "object-assign": "^4", "vary": "^1" - }, - "dependencies": { - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - } } }, "crc-32": { @@ -2050,17 +2059,6 @@ "rndm": "~1.1.0", "scmp": "1.0.0", "uid-safe": "~1.1.0" - }, - "dependencies": { - "uid-safe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-1.1.0.tgz", - "integrity": "sha1-WNbF2r+N+9jVKDSDmAbAP9YUMjI=", - "requires": { - "base64-url": "1.2.1", - "native-or-bluebird": "~1.1.2" - } - } } }, "css-select": { @@ -2123,9 +2121,9 @@ "integrity": "sha512-ZdS2KrgoLxm1guL9XhuaZX223Tiorldvl9QC0M/gihcrJavNDokAp/rX3CyyRHpK+rImxEE3L47cHe7npQMkkg==" }, "csv-parse": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.3.3.tgz", - "integrity": "sha512-bZ+AZjm2LlWEp5+TKeFeXDldduCUUaxEif+KUv+zvAwmCvCKTqeSHVEyxztGCQ6OE+87ObRq4NsCmg91SuJbhQ==" + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.3.4.tgz", + "integrity": "sha512-M1R4WL+vt81+GnkKzi0s1qQM6WXvHQKDecNkpozzAEG8LHvIW9bq5eBnOKFQn50fTuAos7JodBh/07MK+J6G2Q==" }, "csv-stringify": { "version": "5.3.0", @@ -2178,9 +2176,19 @@ "integrity": "sha1-sTS1XqUksvTxibg7+9Z7ZRyjUzI=" }, "data-uri-to-buffer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", - "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.1.tgz", + "integrity": "sha512-OkVVLrerfAKZlW2ZZ3Ve2y65jgiWqBKsTfUIAFbn8nVbPcCZg6l6gikKlEYv0kXcmzqGm6mFq/Jf2vriuEkv8A==", + "requires": { + "@types/node": "^8.0.7" + }, + "dependencies": { + "@types/node": { + "version": "8.10.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.45.tgz", + "integrity": "sha512-tGVTbA+i3qfXsLbq9rEq/hezaHY55QxQLeXQL2ejNgFAxxrgu8eMmYIOsRcl7hN1uTLVsKOOYacV/rcJM3sfgQ==" + } + } }, "data2xml": { "version": "0.8.1", @@ -2342,15 +2350,9 @@ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.3.tgz", + "integrity": "sha1-tDO0ck5x/YVR2YhRdIUcX8N34sk=" }, "detect-indent": { "version": "5.0.0", @@ -2385,6 +2387,12 @@ "requires": { "pify": "^3.0.0" } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true } } }, @@ -2624,11 +2632,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" }, - "component-emitter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz", - "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=" - }, "debug": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", @@ -2706,29 +2709,10 @@ "escape-html": "1.0.1" }, "dependencies": { - "accepts": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.0.7.tgz", - "integrity": "sha1-W1AftPBwQwmWTM2wSBclQSCNqxo=", - "requires": { - "mime-types": "~1.0.0", - "negotiator": "0.4.7" - } - }, "escape-html": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz", "integrity": "sha1-GBoobq05ejmpKFfPsdQwUuNWv/A=" - }, - "mime-types": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", - "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=" - }, - "negotiator": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.7.tgz", - "integrity": "sha1-pBYPcXfsgGc4Yx0NMFIyXaQqvcg=" } } }, @@ -2789,12 +2773,28 @@ "estraverse": "~1.5.0", "esutils": "~1.0.0", "source-map": "~0.1.33" + }, + "dependencies": { + "esprima": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.1.1.tgz", + "integrity": "sha1-W28VR/TRAuZw4UDFCb5ncdautUk=" + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + } } }, "eslint": { - "version": "5.15.3", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.15.3.tgz", - "integrity": "sha512-vMGi0PjCHSokZxE0NLp2VneGw5sio7SSiDNgIUn2tC0XkWJRNOIoHIg3CliLVfXnJsiHxGAYrkw0PieAu8+KYQ==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", + "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -2817,7 +2817,7 @@ "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "inquirer": "^6.2.2", - "js-yaml": "^3.12.0", + "js-yaml": "^3.13.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", "lodash": "^4.17.11", @@ -2836,10 +2836,10 @@ }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true }, "ansi-styles": { "version": "3.2.1", @@ -2861,12 +2861,6 @@ "supports-color": "^5.3.0" } }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -2895,17 +2889,6 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, - "external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, "glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", @@ -2920,47 +2903,6 @@ "path-is-absolute": "^1.0.0" } }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inquirer": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz", - "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==", - "dev": true, - "requires": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.11", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -2974,14 +2916,6 @@ "dev": true, "requires": { "ansi-regex": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } } }, "supports-color": { @@ -3043,6 +2977,18 @@ "supports-hyperlinks": "^1.0.1" }, "dependencies": { + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -3063,6 +3009,31 @@ "supports-color": "^5.3.0" } }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -3158,9 +3129,9 @@ }, "dependencies": { "ignore": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.0.5.tgz", - "integrity": "sha512-kOC8IUb8HSDMVcYrDVezCxpJkzSQWTAzf3olpKM6o9rM5zpojx23O0Fl8Wr4+qJ6ZbPEHqf1fdwev/DS7v7pmA==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.0.6.tgz", + "integrity": "sha512-/+hp3kUf/Csa32ktIaj0OlRqQxrgs30n62M90UBpNd9k+ENEch5S+hmbW3DtcJGz3sYFTh4F3A6fQ0q7KWsp4w==", "dev": true } } @@ -3234,9 +3205,9 @@ }, "dependencies": { "ignore": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.0.5.tgz", - "integrity": "sha512-kOC8IUb8HSDMVcYrDVezCxpJkzSQWTAzf3olpKM6o9rM5zpojx23O0Fl8Wr4+qJ6ZbPEHqf1fdwev/DS7v7pmA==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.0.6.tgz", + "integrity": "sha512-/+hp3kUf/Csa32ktIaj0OlRqQxrgs30n62M90UBpNd9k+ENEch5S+hmbW3DtcJGz3sYFTh4F3A6fQ0q7KWsp4w==", "dev": true } } @@ -3251,9 +3222,9 @@ } }, "eslint-plugin-promise": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.0.1.tgz", - "integrity": "sha512-Si16O0+Hqz1gDHsys6RtFRrW7cCTB6P7p3OJmKp3Y3dxpQE2qwOA7d3xnV+0mBmrPoi0RBnxlCKvqu70te6wjg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.1.1.tgz", + "integrity": "sha512-faAHw7uzlNPy7b45J1guyjazw28M+7gJokKUjC5JSFoYfUEyy6Gw/i7YQvmv2Yk00sUjWcmzXQLpU1Ki/C2IZQ==", "dev": true }, "eslint-plugin-unicorn": { @@ -3284,9 +3255,9 @@ } }, "eslint-rule-docs": { - "version": "1.1.72", - "resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.72.tgz", - "integrity": "sha512-hgixyBXm/ZLfUOcMrWTn2kTiCTzRVmt4JAvYv2eug+8ZV2oR10CbYOMokLgU9XzTYWjPoFAjegZbocH7gsT/Mg==", + "version": "1.1.91", + "resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.91.tgz", + "integrity": "sha512-+67xvP//sxF6OSQQt3Aw9wmH2FfO9D5dPPJC/T7yV2qoasWymbEaASJ72+x3eBC3VFv17rzobSb1csHWxSYDAg==", "dev": true }, "eslint-scope": { @@ -3336,9 +3307,9 @@ } }, "esprima": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.1.1.tgz", - "integrity": "sha1-W28VR/TRAuZw4UDFCb5ncdautUk=" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "espurify": { "version": "1.8.1", @@ -3431,7 +3402,7 @@ } }, "ethercalc-client": { - "version": "git+https://github.com/oaeproject/ethercalc-client.git#c45ad7292a892a6fdb32d4343fc655ee5dadf205", + "version": "git+https://github.com/oaeproject/ethercalc-client.git#936ddbb452da4339aa48d8494e94a946c3f50cff", "from": "git+https://github.com/oaeproject/ethercalc-client.git", "requires": { "axios": "^0.18.0", @@ -3462,12 +3433,12 @@ } }, "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", @@ -3476,11 +3447,13 @@ }, "dependencies": { "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } @@ -3524,15 +3497,6 @@ } } }, - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, "expand-url": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/expand-url/-/expand-url-0.1.3.tgz", @@ -3578,6 +3542,20 @@ "vary": "~1.1.2" }, "dependencies": { + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, "finalhandler": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", @@ -3592,6 +3570,81 @@ "unpipe": "~1.0.0" } }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "ipaddr.js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "proxy-addr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -3684,13 +3737,23 @@ } }, "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } } }, "extglob": { @@ -3782,12 +3845,6 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -3940,68 +3997,16 @@ } }, "find-cache-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz", - "integrity": "sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", "dev": true, "requires": { "commondir": "^1.0.1", - "make-dir": "^1.0.0", + "make-dir": "^2.0.0", "pkg-dir": "^3.0.0" }, "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", - "dev": true - }, "pkg-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", @@ -4014,34 +4019,12 @@ } }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "^2.0.0" - } - }, - "findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } + "locate-path": "^3.0.0" } }, "flat": { @@ -4078,11 +4061,6 @@ "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", "dev": true }, - "flex-exec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/flex-exec/-/flex-exec-1.0.0.tgz", - "integrity": "sha1-BpdLaFMoOdKhLDLevNsSN4IA/fA=" - }, "follow-redirects": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", @@ -4165,9 +4143,9 @@ } }, "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.2.2.tgz", + "integrity": "sha1-lzHc9WeMf660T7kDxPct9VGH+nc=" }, "fs-constants": { "version": "1.0.0", @@ -4217,25 +4195,29 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true, "optional": true }, "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, "optional": true, "requires": { @@ -4245,13 +4227,15 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true, "optional": true }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "optional": true, "requires": { @@ -4261,37 +4245,43 @@ }, "chownr": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true, "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true, "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true, "optional": true }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, "optional": true }, "debug": { "version": "2.6.9", - "bundled": true, + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "optional": true, "requires": { @@ -4300,25 +4290,29 @@ }, "deep-extend": { "version": "0.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, "optional": true }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "dev": true, "optional": true, "requires": { @@ -4327,13 +4321,15 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true, "requires": { @@ -4349,7 +4345,8 @@ }, "glob": { "version": "7.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "optional": true, "requires": { @@ -4363,13 +4360,15 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.24", - "bundled": true, + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "optional": true, "requires": { @@ -4378,7 +4377,8 @@ }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, "optional": true, "requires": { @@ -4387,7 +4387,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "optional": true, "requires": { @@ -4397,19 +4398,22 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true, "optional": true }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "optional": true, "requires": { @@ -4418,13 +4422,15 @@ }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "optional": true, "requires": { @@ -4433,13 +4439,15 @@ }, "minimist": { "version": "0.0.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true, "optional": true }, "minipass": { "version": "2.3.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, "optional": true, "requires": { @@ -4449,7 +4457,8 @@ }, "minizlib": { "version": "1.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "dev": true, "optional": true, "requires": { @@ -4458,7 +4467,8 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "optional": true, "requires": { @@ -4467,13 +4477,15 @@ }, "ms": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true, "optional": true }, "needle": { "version": "2.2.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.4.tgz", + "integrity": "sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==", "dev": true, "optional": true, "requires": { @@ -4484,7 +4496,8 @@ }, "node-pre-gyp": { "version": "0.10.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz", + "integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==", "dev": true, "optional": true, "requires": { @@ -4502,7 +4515,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, "requires": { @@ -4512,13 +4526,15 @@ }, "npm-bundled": { "version": "1.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz", + "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.2.0.tgz", + "integrity": "sha512-7Mni4Z8Xkx0/oegoqlcao/JpPCPEMtUvsmB0q7mgvlMinykJLSRTYuFqoQLYgGY8biuxIeiHO+QNJKbCfljewQ==", "dev": true, "optional": true, "requires": { @@ -4528,7 +4544,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "optional": true, "requires": { @@ -4540,19 +4557,22 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "optional": true, "requires": { @@ -4561,19 +4581,22 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "optional": true, "requires": { @@ -4583,19 +4606,22 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true }, "rc": { "version": "1.2.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "optional": true, "requires": { @@ -4607,7 +4633,8 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true } @@ -4615,7 +4642,8 @@ }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, "requires": { @@ -4630,7 +4658,8 @@ }, "rimraf": { "version": "2.6.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "optional": true, "requires": { @@ -4639,43 +4668,50 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "optional": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, "optional": true }, "semver": { "version": "5.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "optional": true, "requires": { @@ -4686,7 +4722,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, "requires": { @@ -4695,7 +4732,8 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "optional": true, "requires": { @@ -4704,13 +4742,15 @@ }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true }, "tar": { "version": "4.4.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "dev": true, "optional": true, "requires": { @@ -4725,13 +4765,15 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true }, "wide-align": { "version": "1.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "optional": true, "requires": { @@ -4740,13 +4782,15 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true, "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true, "optional": true } @@ -4853,10 +4897,10 @@ "sshpk": "^1.7.0" } }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "request": { "version": "2.88.0", @@ -4901,11 +4945,6 @@ "requires": { "safe-buffer": "^5.0.1" } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" } } }, @@ -4922,31 +4961,6 @@ "string-width": "^1.0.1", "strip-ansi": "^3.0.1", "wide-align": "^1.1.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } } }, "generate-function": { @@ -4966,9 +4980,9 @@ } }, "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, "get-func-name": { @@ -4989,51 +5003,64 @@ "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" }, "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } }, "get-uri": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.2.tgz", - "integrity": "sha512-ZD325dMZOgerGqF/rF6vZXyFGTAay62svjQIT+X/oU2PtxYpFxvSkbsdi+oxIrsNxlZVd4y8wUDqkaExWTI/Cw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.3.tgz", + "integrity": "sha512-x5j6Ks7FOgLD/GlvjKwgu7wdmMR55iuRHhn8hj/+gA+eSbxQvZ+AEomq+3MgVEZj1vpi738QahGbCCSIDtXtkw==", "requires": { - "data-uri-to-buffer": "1", - "debug": "2", - "extend": "3", + "data-uri-to-buffer": "2", + "debug": "4", + "extend": "~3.0.2", "file-uri-to-path": "1", "ftp": "~0.3.10", - "readable-stream": "2" + "readable-stream": "3" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", + "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", "requires": { "safe-buffer": "~5.1.0" } @@ -5060,15 +5087,6 @@ } } }, - "gift": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/gift/-/gift-0.10.2.tgz", - "integrity": "sha512-wC9aKnQpjfOTWX+JG4DPJkS89ux6sl8EN4hXhv/2vBoXCDTEz1JiTeGTSeuKYlCqIgUFM1JwPVym34Sys3hvzw==", - "requires": { - "flex-exec": "^1.0.0", - "underscore": "^1.8.3" - } - }, "glob": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", @@ -5121,30 +5139,6 @@ "ini": "^1.3.4" } }, - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - } - }, - "global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - } - }, "globalize": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/globalize/-/globalize-0.1.1.tgz", @@ -5157,14 +5151,14 @@ "dev": true }, "globby": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-9.1.0.tgz", - "integrity": "sha512-VtYjhHr7ncls724Of5W6Kaahz0ag7dB4G62/2HsN+xEKG6SrPzM1AJMerGxQTwJGnN9reeyxdvXbuZYpfssCvg==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", + "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", "dev": true, "requires": { "@types/glob": "^7.1.1", "array-union": "^1.0.2", - "dir-glob": "^2.2.1", + "dir-glob": "^2.2.2", "fast-glob": "^2.2.6", "glob": "^7.1.3", "ignore": "^4.0.3", @@ -5185,12 +5179,6 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true } } }, @@ -5357,6 +5345,26 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, "qs": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.3.tgz", @@ -5401,21 +5409,6 @@ "stringstream": "~0.0.4", "tough-cookie": "~2.3.0", "tunnel-agent": "~0.4.1" - }, - "dependencies": { - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", - "requires": { - "punycode": "^1.4.1" - } - } } }, "sntp": { @@ -5430,6 +5423,14 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=" + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "requires": { + "punycode": "^1.4.1" + } } } }, @@ -5578,6 +5579,21 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, "qs": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.1.2.tgz", @@ -5622,13 +5638,6 @@ "stringstream": "~0.0.4", "tough-cookie": "~2.2.0", "tunnel-agent": "~0.4.1" - }, - "dependencies": { - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - } } }, "sntp": { @@ -5662,6 +5671,13 @@ "timed-out": "^4.0.0", "unzip-response": "^2.0.1", "url-parse-lax": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + } } }, "graceful-fs": { @@ -5761,10 +5777,10 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "request": { "version": "2.88.0", @@ -5809,11 +5825,6 @@ "requires": { "safe-buffer": "^5.0.1" } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" } } }, @@ -5838,15 +5849,15 @@ "requires": { "babyparse": "0.2.1", "codepage": "^1.14.0", - "commander": "^2.19.0", + "commander": "^2.20.0", "exit-on-epipe": "^1.0.1", "ssf": "0.8.2" }, "dependencies": { "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" } } }, @@ -5955,15 +5966,6 @@ "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=" }, - "homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "requires": { - "parse-passwd": "^1.0.0" - } - }, "hosted-git-info": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", @@ -5994,9 +5996,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", - "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", + "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -6192,26 +6194,30 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz", + "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==", "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^2.0.4", + "external-editor": "^3.0.3", "figures": "^2.0.0", - "lodash": "^4.3.0", + "lodash": "^4.17.11", "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", + "rxjs": "^6.4.0", "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", + "strip-ansi": "^5.0.0", "through": "^2.3.6" }, "dependencies": { + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", @@ -6235,12 +6241,43 @@ "supports-color": "^5.3.0" } }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + } } }, "supports-color": { @@ -6280,9 +6317,9 @@ "integrity": "sha1-RMWp23njnUBSAbTXjROzhw5I2zE=" }, "ipaddr.js": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", - "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-0.1.2.tgz", + "integrity": "sha1-ah/T2FT1ACllw017vNm0qNSwRn4=" }, "irregular-plurals": { "version": "2.0.0", @@ -6408,9 +6445,12 @@ } }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } }, "is-get-set-prop": { "version": "1.0.0", @@ -6423,9 +6463,9 @@ } }, "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { "is-extglob": "^2.1.1" @@ -6606,14 +6646,6 @@ "dev": true, "requires": { "punycode": "2.x.x" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - } } }, "isexe": { @@ -6657,7 +6689,7 @@ "resolved": "https://registry.npmjs.org/j/-/j-0.4.5.tgz", "integrity": "sha1-As8p8d2+VOUnJj0HVNbo0hemBk4=", "requires": { - "commander": "^2.19.0", + "commander": "^2.20.0", "concat-stream": "^2.0.0", "exit-on-epipe": "^1.0.1", "harb": "~0.1.1", @@ -6666,38 +6698,9 @@ }, "dependencies": { "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" - }, - "concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "readable-stream": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.2.0.tgz", - "integrity": "sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "string_decoder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", - "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", - "requires": { - "safe-buffer": "~5.1.0" - } + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" } } }, @@ -6795,6 +6798,13 @@ "jison-lex": "0.3.x", "lex-parser": "~0.1.3", "nomnom": "1.5.2" + }, + "dependencies": { + "esprima": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.1.1.tgz", + "integrity": "sha1-W28VR/TRAuZw4UDFCb5ncdautUk=" + } } }, "jison-lex": { @@ -6838,19 +6848,12 @@ "dev": true }, "js-yaml": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz", - "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" - }, - "dependencies": { - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - } } }, "jsbn": { @@ -6912,9 +6915,9 @@ "integrity": "sha1-9u/JPAagTemuxTBT3yVZuxniA4s=" }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", "requires": { "minimist": "^1.2.0" }, @@ -6990,11 +6993,6 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -7057,9 +7055,9 @@ } }, "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" }, "cross-spawn": { "version": "6.0.5", @@ -7099,6 +7097,27 @@ "requires": { "base64url": "~1.0.4", "jwa": "~1.0.0" + }, + "dependencies": { + "base64url": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-1.0.6.tgz", + "integrity": "sha1-1k03XWinxkDZEuI1jRcNylu1RoE=", + "requires": { + "concat-stream": "~1.4.7", + "meow": "~2.0.0" + } + }, + "concat-stream": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", + "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.9", + "typedarray": "~0.0.5" + } + } } }, "keygrip": { @@ -7164,9 +7183,9 @@ }, "dependencies": { "@types/node": { - "version": "10.12.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.26.tgz", - "integrity": "sha512-nMRqS+mL1TOnIJrL6LKJcNZPB8V3eTfRo9FQA2b5gDvrHurC8XbSA86KNe0dShlEL7ReWJv/OU9NL7Z0dnqWTg==" + "version": "10.14.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.4.tgz", + "integrity": "sha512-DT25xX/YgyPKiHFOpNuANIQIVvYEwCWXgK2jYYwqgaMrYE6+tq+DtmMwlD3drl6DJbUwtlIDnn0d7tIn/EbXBg==" }, "lru-cache": { "version": "5.1.1", @@ -7269,6 +7288,15 @@ "tough-cookie": ">=0.12.0", "tunnel-agent": "~0.4.0" } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } } } }, @@ -7316,13 +7344,6 @@ "optionator": "~0.8.1", "prelude-ls": "~1.1.2", "source-map": "^0.5.6" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } } }, "load-json-file": { @@ -7359,6 +7380,21 @@ "big.js": "^5.2.2", "emojis-list": "^2.0.0", "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } } }, "locale": { @@ -7367,11 +7403,12 @@ "integrity": "sha1-O1v3BhT9q0isPj+8ZIFHy2VEO94=" }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, "requires": { - "p-locate": "^2.0.0", + "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, @@ -7598,9 +7635,9 @@ "integrity": "sha1-XJJ2ySyRrDWpJBtQGNRnI9kuL18=" }, "macos-release": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.1.0.tgz", - "integrity": "sha512-8TCbwvN1mfNxbBv0yBtfyIFMo3m1QsNbKHv7PYIp/abRBKVQBXN7ecu3aeGGgT18VC/Tf397LBDGZF9KBGJFFw==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.2.0.tgz", + "integrity": "sha512-iV2IDxZaX8dIcM7fG6cI46uNmHUxHE4yN+Z8tKHAW1TBPMZDIKHf/3L+YnOuj/FK9il14UaVdHmiQ1tsi90ltA==" }, "mailparser": { "version": "0.6.2", @@ -7627,15 +7664,13 @@ "requires": { "pify": "^4.0.1", "semver": "^5.6.0" - }, - "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - } } }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==" + }, "map-age-cleaner": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", @@ -7676,9 +7711,9 @@ } }, "marked": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.1.tgz", - "integrity": "sha512-+H0L3ibcWhAZE02SKMqmvYsErLo4EAVJxu5h3bHBBDvvjeWXtl92rGUSBYHL2++5Y+RSNgl8dYOAXcYe7lp1fA==" + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.2.tgz", + "integrity": "sha512-LqxwVH3P/rqKX4EKGz7+c2G9r98WeM/SW34ybhgNGhUQNKtf1GmmSkJ6cDGJ/t6tiyae49qRkpyTw2B9HOrgUA==" }, "mdurl": { "version": "1.0.1", @@ -7691,14 +7726,22 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "mem": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", - "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "dev": true, "requires": { "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", + "mimic-fn": "^2.0.0", "p-is-promise": "^2.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + } } }, "mensch": { @@ -7721,6 +7764,11 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "object-assign": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-1.0.0.tgz", + "integrity": "sha1-5l3Idm07R7S4MHRlyDEdoDCwcKY=" } } }, @@ -7730,9 +7778,9 @@ "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==" }, "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-0.0.2.tgz", + "integrity": "sha1-w2pSp4FDdRPFcnXzndnTF1FKyMc=" }, "merge2": { "version": "1.2.3", @@ -7759,11 +7807,6 @@ "ms": "0.6.2" } }, - "methods": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.0.tgz", - "integrity": "sha1-XcpO4S31L/OwVhRZhqjwHLyGQ28=" - }, "ms": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", @@ -7777,9 +7820,9 @@ } }, "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.0.tgz", + "integrity": "sha1-XcpO4S31L/OwVhRZhqjwHLyGQ28=" }, "micromatch": { "version": "3.1.10", @@ -7802,9 +7845,9 @@ } }, "mime": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", - "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==" + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.2.tgz", + "integrity": "sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg==" }, "mime-db": { "version": "1.38.0", @@ -7934,9 +7977,9 @@ "integrity": "sha512-UaahPNLllQsstHOEHAmVnTHCMQrAS9eL5Qgdi50QrYz6UgGk+Xziz2udz2GN6NYcyODcPLnasC7a7s6R2DjiaQ==" }, "mocha": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.0.2.tgz", - "integrity": "sha512-RtTJsmmToGyeTznSOMoM6TPEk1A84FQaHIciKrRqARZx+B5ccJ5tXlmJzEKGBxZdqk9UjpRsesZTUkZmR5YnuQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.1.tgz", + "integrity": "sha512-ayfr68s4kyDnCU0hjkTk5Z8J8dqr1iPUuVjmd+dLFgaGKOPlgx1XrOGn5k3H1LlXNnLBb8voZMYMKxchiA4Ujg==", "dev": true, "requires": { "ansi-colors": "3.2.3", @@ -7944,11 +7987,11 @@ "debug": "3.2.6", "diff": "3.5.0", "escape-string-regexp": "1.0.5", - "findup-sync": "2.0.0", + "find-up": "3.0.0", "glob": "7.1.3", "growl": "1.10.5", "he": "1.2.0", - "js-yaml": "3.12.0", + "js-yaml": "3.13.0", "log-symbols": "2.2.0", "minimatch": "3.0.4", "mkdirp": "0.5.1", @@ -7959,8 +8002,8 @@ "supports-color": "6.0.0", "which": "1.3.1", "wide-align": "1.1.3", - "yargs": "12.0.5", - "yargs-parser": "11.1.1", + "yargs": "13.2.2", + "yargs-parser": "13.0.0", "yargs-unparser": "1.5.0" }, "dependencies": { @@ -7979,19 +8022,18 @@ "string-width": "^2.1.1", "strip-ansi": "^4.0.0", "wrap-ansi": "^2.0.0" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + }, + "dependencies": { + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + } } }, "debug": { @@ -8009,45 +8051,6 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", @@ -8068,10 +8071,16 @@ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, "js-yaml": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", - "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -8087,16 +8096,6 @@ "invert-kv": "^2.0.0" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -8114,30 +8113,34 @@ "mem": "^4.0.0" } }, - "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } } }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", - "dev": true - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -8156,24 +8159,29 @@ "has-flag": "^3.0.0" } }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", "dev": true, "requires": { "cliui": "^4.0.0", - "decamelize": "^1.2.0", "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^2.0.0", + "string-width": "^3.0.0", "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" } } } @@ -8263,16 +8271,29 @@ }, "dependencies": { "http-errors": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.1.tgz", - "integrity": "sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "requires": { "depd": "~1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.1.0", + "setprototypeof": "1.1.1", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.0" } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } } } }, @@ -8314,9 +8335,9 @@ } }, "nan": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", - "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==" + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==" }, "nanoid": { "version": "2.0.1", @@ -8400,9 +8421,9 @@ } }, "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.7.tgz", + "integrity": "sha1-pBYPcXfsgGc4Yx0NMFIyXaQqvcg=" }, "netmask": { "version": "1.0.6", @@ -8467,6 +8488,11 @@ "resolved": "https://registry.npmjs.org/node-esapi/-/node-esapi-0.0.1.tgz", "integrity": "sha1-b0xFahTrvRNaDw0XZsMLKr8HGYw=" }, + "node-fetch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", + "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" + }, "node-forge": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", @@ -8562,13 +8588,10 @@ "sshpk": "^1.7.0" } }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "requires": { - "abbrev": "1" - } + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "request": { "version": "2.88.0", @@ -8669,9 +8692,9 @@ } }, "node-uuid": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.1.tgz", + "integrity": "sha1-Oa71EOWImj3KnIlbUGxzquG6wEg=" }, "nodegit": { "version": "0.24.1", @@ -8688,21 +8711,6 @@ "ramda": "^0.25.0", "request-promise-native": "^1.0.5", "tar-fs": "^1.16.3" - }, - "dependencies": { - "json5": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", - "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", - "requires": { - "minimist": "^1.2.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } } }, "nodegit-promise": { @@ -8946,6 +8954,11 @@ "underscore": "1.1.x" }, "dependencies": { + "colors": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", + "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=" + }, "underscore": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.1.7.tgz", @@ -8954,10 +8967,9 @@ } }, "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dev": true, + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "requires": { "abbrev": "1" } @@ -9060,12 +9072,14 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "append-transform": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", "dev": true, "requires": { "default-require-extensions": "^2.0.0" @@ -9073,17 +9087,20 @@ }, "archy": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, "arrify": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, "async": { "version": "2.6.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", "dev": true, "requires": { "lodash": "^4.17.11" @@ -9091,12 +9108,14 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -9105,7 +9124,8 @@ }, "caching-transform": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.1.tgz", + "integrity": "sha512-Y1KTLNwSPd4ljsDrFOtyXVmm7Gnk42yQitNq43AhE+cwUR/e4T+rmOHs1IPtzBg8066GBJfTOj1rQYFSWSsH2g==", "dev": true, "requires": { "hasha": "^3.0.0", @@ -9116,12 +9136,14 @@ }, "camelcase": { "version": "5.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", "dev": true }, "cliui": { "version": "4.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { "string-width": "^2.1.1", @@ -9131,28 +9153,33 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, "commander": { "version": "2.17.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", "dev": true, "optional": true }, "commondir": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "convert-source-map": { "version": "1.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", "dev": true, "requires": { "safe-buffer": "~5.1.1" @@ -9160,7 +9187,8 @@ }, "cross-spawn": { "version": "4.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", "dev": true, "requires": { "lru-cache": "^4.0.1", @@ -9169,7 +9197,8 @@ }, "debug": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" @@ -9177,12 +9206,14 @@ }, "decamelize": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, "default-require-extensions": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", "dev": true, "requires": { "strip-bom": "^3.0.0" @@ -9190,7 +9221,8 @@ }, "end-of-stream": { "version": "1.4.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { "once": "^1.4.0" @@ -9198,7 +9230,8 @@ }, "error-ex": { "version": "1.3.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { "is-arrayish": "^0.2.1" @@ -9206,12 +9239,14 @@ }, "es6-error": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, "execa": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { "cross-spawn": "^6.0.0", @@ -9225,7 +9260,8 @@ "dependencies": { "cross-spawn": { "version": "6.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { "nice-try": "^1.0.4", @@ -9239,7 +9275,8 @@ }, "find-cache-dir": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz", + "integrity": "sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==", "dev": true, "requires": { "commondir": "^1.0.1", @@ -9249,7 +9286,8 @@ }, "find-up": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { "locate-path": "^3.0.0" @@ -9257,7 +9295,8 @@ }, "foreground-child": { "version": "1.5.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", + "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", "dev": true, "requires": { "cross-spawn": "^4", @@ -9266,17 +9305,20 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "get-caller-file": { "version": "1.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, "get-stream": { "version": "4.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, "requires": { "pump": "^3.0.0" @@ -9284,7 +9326,8 @@ }, "glob": { "version": "7.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -9297,12 +9340,14 @@ }, "graceful-fs": { "version": "4.1.15", - "bundled": true, + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", "dev": true }, "handlebars": { "version": "4.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", + "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", "dev": true, "requires": { "async": "^2.5.0", @@ -9313,19 +9358,22 @@ "dependencies": { "source-map": { "version": "0.6.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, "has-flag": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "hasha": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", + "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", "dev": true, "requires": { "is-stream": "^1.0.1" @@ -9333,17 +9381,20 @@ }, "hosted-git-info": { "version": "2.7.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, "imurmurhash": { "version": "0.1.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { "once": "^1.3.0", @@ -9352,42 +9403,50 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "invert-kv": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, "is-arrayish": { "version": "0.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, "is-stream": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, "isexe": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "istanbul-lib-coverage": { "version": "2.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==", "dev": true }, "istanbul-lib-hook": { "version": "2.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.3.tgz", + "integrity": "sha512-CLmEqwEhuCYtGcpNVJjLV1DQyVnIqavMLFHV/DP+np/g3qvdxu3gsPqYoJMXm15sN84xOlckFB3VNvRbf5yEgA==", "dev": true, "requires": { "append-transform": "^1.0.0" @@ -9395,7 +9454,8 @@ }, "istanbul-lib-report": { "version": "2.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.4.tgz", + "integrity": "sha512-sOiLZLAWpA0+3b5w5/dq0cjm2rrNdAfHWaGhmn7XEFW6X++IV9Ohn+pnELAl9K3rfpaeBfbmH9JU5sejacdLeA==", "dev": true, "requires": { "istanbul-lib-coverage": "^2.0.3", @@ -9405,7 +9465,8 @@ "dependencies": { "supports-color": { "version": "6.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -9415,7 +9476,8 @@ }, "istanbul-lib-source-maps": { "version": "3.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.2.tgz", + "integrity": "sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ==", "dev": true, "requires": { "debug": "^4.1.1", @@ -9427,14 +9489,16 @@ "dependencies": { "source-map": { "version": "0.6.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, "istanbul-reports": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.1.1.tgz", + "integrity": "sha512-FzNahnidyEPBCI0HcufJoSEoKykesRlFcSzQqjH9x0+LC8tnnE/p/90PBLu8iZTxr8yYZNyTtiAujUqyN+CIxw==", "dev": true, "requires": { "handlebars": "^4.1.0" @@ -9442,12 +9506,14 @@ }, "json-parse-better-errors": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, "lcid": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { "invert-kv": "^2.0.0" @@ -9455,7 +9521,8 @@ }, "load-json-file": { "version": "4.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -9466,7 +9533,8 @@ }, "locate-path": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { "p-locate": "^3.0.0", @@ -9475,17 +9543,20 @@ }, "lodash": { "version": "4.17.11", - "bundled": true, + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, "lodash.flattendeep": { "version": "4.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, "lru-cache": { "version": "4.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { "pseudomap": "^1.0.2", @@ -9494,7 +9565,8 @@ }, "make-dir": { "version": "1.3.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, "requires": { "pify": "^3.0.0" @@ -9502,7 +9574,8 @@ }, "map-age-cleaner": { "version": "0.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "dev": true, "requires": { "p-defer": "^1.0.0" @@ -9510,7 +9583,8 @@ }, "mem": { "version": "4.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", "dev": true, "requires": { "map-age-cleaner": "^0.1.1", @@ -9520,7 +9594,8 @@ }, "merge-source-map": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", "dev": true, "requires": { "source-map": "^0.6.1" @@ -9528,19 +9603,22 @@ "dependencies": { "source-map": { "version": "0.6.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, "mimic-fn": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -9548,12 +9626,14 @@ }, "minimist": { "version": "0.0.10", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { "minimist": "0.0.8" @@ -9561,24 +9641,28 @@ "dependencies": { "minimist": { "version": "0.0.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true } } }, "ms": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, "nice-try": { "version": "1.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, "normalize-package-data": { "version": "2.5.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { "hosted-git-info": "^2.1.4", @@ -9589,7 +9673,8 @@ }, "npm-run-path": { "version": "2.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { "path-key": "^2.0.0" @@ -9597,12 +9682,14 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" @@ -9610,7 +9697,8 @@ }, "optimist": { "version": "0.6.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { "minimist": "~0.0.1", @@ -9619,12 +9707,14 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, "os-locale": { "version": "3.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { "execa": "^1.0.0", @@ -9634,22 +9724,26 @@ }, "p-defer": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, "p-finally": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, "p-is-promise": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", "dev": true }, "p-limit": { "version": "2.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -9657,7 +9751,8 @@ }, "p-locate": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { "p-limit": "^2.0.0" @@ -9665,12 +9760,14 @@ }, "p-try": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", "dev": true }, "package-hash": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", + "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", "dev": true, "requires": { "graceful-fs": "^4.1.15", @@ -9681,7 +9778,8 @@ }, "parse-json": { "version": "4.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { "error-ex": "^1.3.1", @@ -9690,27 +9788,32 @@ }, "path-exists": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "path-key": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, "path-parse": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "path-type": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { "pify": "^3.0.0" @@ -9718,12 +9821,14 @@ }, "pify": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, "pkg-dir": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", "dev": true, "requires": { "find-up": "^3.0.0" @@ -9731,12 +9836,14 @@ }, "pseudomap": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, "pump": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { "end-of-stream": "^1.1.0", @@ -9745,7 +9852,8 @@ }, "read-pkg": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { "load-json-file": "^4.0.0", @@ -9755,7 +9863,8 @@ }, "read-pkg-up": { "version": "4.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", "dev": true, "requires": { "find-up": "^3.0.0", @@ -9764,7 +9873,8 @@ }, "release-zalgo": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", "dev": true, "requires": { "es6-error": "^4.0.1" @@ -9772,17 +9882,20 @@ }, "require-directory": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, "require-main-filename": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, "resolve": { "version": "1.10.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -9790,12 +9903,14 @@ }, "resolve-from": { "version": "4.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, "rimraf": { "version": "2.6.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "requires": { "glob": "^7.1.3" @@ -9803,22 +9918,26 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, "semver": { "version": "5.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", "dev": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, "shebang-command": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { "shebang-regex": "^1.0.0" @@ -9826,17 +9945,20 @@ }, "shebang-regex": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, "spawn-wrap": { "version": "1.4.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.2.tgz", + "integrity": "sha512-vMwR3OmmDhnxCVxM8M+xO/FtIp6Ju/mNaDfCMMW7FDcLRTPFWUswec4LXJHTJE2hwTI9O0YBfygu4DalFl7Ylg==", "dev": true, "requires": { "foreground-child": "^1.5.6", @@ -9849,7 +9971,8 @@ }, "spdx-correct": { "version": "3.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -9858,12 +9981,14 @@ }, "spdx-exceptions": { "version": "2.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", "dev": true }, "spdx-expression-parse": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -9872,12 +9997,14 @@ }, "spdx-license-ids": { "version": "3.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", + "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", "dev": true }, "string-width": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -9886,7 +10013,8 @@ }, "strip-ansi": { "version": "4.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { "ansi-regex": "^3.0.0" @@ -9894,17 +10022,20 @@ }, "strip-bom": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, "strip-eof": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, "test-exclude": { "version": "5.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.1.0.tgz", + "integrity": "sha512-gwf0S2fFsANC55fSeSqpb8BYk6w3FDvwZxfNjeF6FRgvFa43r+7wRiA/Q0IxoRU37wB/LE8IQ4221BsNucTaCA==", "dev": true, "requires": { "arrify": "^1.0.1", @@ -9915,7 +10046,8 @@ }, "uglify-js": { "version": "3.4.9", - "bundled": true, + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", "dev": true, "optional": true, "requires": { @@ -9925,7 +10057,8 @@ "dependencies": { "source-map": { "version": "0.6.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "optional": true } @@ -9933,12 +10066,14 @@ }, "uuid": { "version": "3.3.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", "dev": true }, "validate-npm-package-license": { "version": "3.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { "spdx-correct": "^3.0.0", @@ -9947,7 +10082,8 @@ }, "which": { "version": "1.3.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { "isexe": "^2.0.0" @@ -9955,17 +10091,20 @@ }, "which-module": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, "wordwrap": { "version": "0.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", "dev": true }, "wrap-ansi": { "version": "2.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { "string-width": "^1.0.1", @@ -9974,12 +10113,14 @@ "dependencies": { "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { "number-is-nan": "^1.0.0" @@ -9987,7 +10128,8 @@ }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { "code-point-at": "^1.0.0", @@ -9997,7 +10139,8 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -10007,12 +10150,14 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "write-file-atomic": { "version": "2.4.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz", + "integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==", "dev": true, "requires": { "graceful-fs": "^4.1.11", @@ -10022,17 +10167,20 @@ }, "y18n": { "version": "4.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true }, "yallist": { "version": "2.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true }, "yargs": { "version": "12.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "dev": true, "requires": { "cliui": "^4.0.0", @@ -10051,7 +10199,8 @@ }, "yargs-parser": { "version": "11.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -10210,9 +10359,9 @@ "dev": true }, "object-assign": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-1.0.0.tgz", - "integrity": "sha1-5l3Idm07R7S4MHRlyDEdoDCwcKY=" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-component": { "version": "0.0.3", @@ -10248,9 +10397,9 @@ } }, "object-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", - "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, "object-visit": { @@ -10392,10 +10541,10 @@ "sshpk": "^1.7.0" } }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "request": { "version": "2.88.0", @@ -10440,18 +10589,13 @@ "requires": { "safe-buffer": "^5.0.1" } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" } } }, "opn": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz", - "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", "requires": { "is-wsl": "^1.1.0" } @@ -10463,6 +10607,13 @@ "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + } } }, "optionator": { @@ -10476,13 +10627,6 @@ "prelude-ls": "~1.1.2", "type-check": "~0.3.2", "wordwrap": "~1.0.0" - }, - "dependencies": { - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" - } } }, "options": { @@ -10509,11 +10653,11 @@ } }, "os-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.0.0.tgz", - "integrity": "sha512-7c74tib2FsdFbQ3W+qj8Tyd1R3Z6tuVRNNxXjJcZ4NgjIEQU9N/prVMqcW29XZPXGACqaXN3jq58/6hoaoXH6g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz", + "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==", "requires": { - "macos-release": "^2.0.0", + "macos-release": "^2.2.0", "windows-release": "^3.1.0" } }, @@ -10543,36 +10687,39 @@ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-is-promise": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", - "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", "dev": true }, "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, "requires": { - "p-try": "^1.0.0" + "p-try": "^2.0.0" } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.0.0" } }, "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "pac-proxy-agent": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-2.0.2.tgz", - "integrity": "sha512-cDNAN1Ehjbf5EHkNY5qnRhGPUCp6SnpyVof5fRzN800QV1Y2OkzbH9rmjZkbBRa8igof903yOnjIl6z0SlAhxA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-3.0.0.tgz", + "integrity": "sha512-AOUX9jES/EkQX2zRz0AW7lSx9jD//hQS8wFXBvcnd/J2Py9KaMJMqV/LPqJssj1tgGufotb2mmopGPR15ODv1Q==", "requires": { "agent-base": "^4.2.0", "debug": "^3.1.0", @@ -10581,7 +10728,7 @@ "https-proxy-agent": "^2.2.1", "pac-resolver": "^3.0.0", "raw-body": "^2.2.0", - "socks-proxy-agent": "^3.0.0" + "socks-proxy-agent": "^4.0.1" }, "dependencies": { "debug": { @@ -10628,9 +10775,9 @@ "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==" }, "parent-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz", - "integrity": "sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { "callsites": "^3.0.0" @@ -10645,12 +10792,6 @@ "error-ex": "^1.2.0" } }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true - }, "parse5": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", @@ -10712,11 +10853,6 @@ "xml2js": "0.4.4" }, "dependencies": { - "node-uuid": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.1.tgz", - "integrity": "sha1-Oa71EOWImj3KnIlbUGxzquG6wEg=" - }, "passport": { "version": "0.1.18", "resolved": "https://registry.npmjs.org/passport/-/passport-0.1.18.tgz", @@ -10796,9 +10932,9 @@ } }, "passport-ldapauth": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/passport-ldapauth/-/passport-ldapauth-2.1.2.tgz", - "integrity": "sha512-V+oYNhJwW/ncYHS1IX3eVN9TUH38OsD15Cwo8w+FIV+GXKFJRtUhZyt5nNwExyU0ujMya8Rtzm04+4d8BjvBUg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/passport-ldapauth/-/passport-ldapauth-2.1.3.tgz", + "integrity": "sha512-23n425UTasN6XhcXG0qQ0h0YrS/zfo8kNIEhSLfPsNpglhYhhQFfB1pmDc5RrH+Kiz5fKLkki5BpvkKHCwkixg==", "requires": { "@types/node": "^10.12.26", "@types/passport": "^1.0.0", @@ -10807,9 +10943,9 @@ }, "dependencies": { "@types/node": { - "version": "10.12.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.26.tgz", - "integrity": "sha512-nMRqS+mL1TOnIJrL6LKJcNZPB8V3eTfRo9FQA2b5gDvrHurC8XbSA86KNe0dShlEL7ReWJv/OU9NL7Z0dnqWTg==" + "version": "10.14.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.4.tgz", + "integrity": "sha512-DT25xX/YgyPKiHFOpNuANIQIVvYEwCWXgK2jYYwqgaMrYE6+tq+DtmMwlD3drl6DJbUwtlIDnn0d7tIn/EbXBg==" } } }, @@ -10832,10 +10968,11 @@ } }, "passport-oauth2": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.4.0.tgz", - "integrity": "sha1-9i+BWDy+EmCb585vFguTlaJ7hq0=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.5.0.tgz", + "integrity": "sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ==", "requires": { + "base64url": "3.x.x", "oauth": "0.9.x", "passport-strategy": "1.x.x", "uid2": "0.0.x", @@ -10958,9 +11095,9 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, "pinkie": { "version": "2.0.4", @@ -10985,6 +11122,15 @@ "load-json-file": "^4.0.0" }, "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, "graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", @@ -11003,6 +11149,40 @@ "strip-bom": "^3.0.0" } }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -11012,6 +11192,12 @@ "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true } } }, @@ -11022,6 +11208,51 @@ "dev": true, "requires": { "find-up": "^2.1.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } } }, "pkg-up": { @@ -11030,6 +11261,46 @@ "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", "requires": { "find-up": "^2.1.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + } } }, "pkginfo": { @@ -11038,9 +11309,9 @@ "integrity": "sha1-cjnEKl72wwuPMoQ52bn/cQQkkPg=" }, "plur": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/plur/-/plur-3.0.1.tgz", - "integrity": "sha512-lJl0ojUynAM1BZn58Pas2WT/TXeC1+bS+UqShl0x9+49AtOn7DixRXVzaC8qrDOIxNDmepKnLuMTH7NQmkX0PA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/plur/-/plur-3.1.1.tgz", + "integrity": "sha512-t1Ax8KUvV3FFII8ltczPn2tJdjqbd1sIzu6t4JL7nQ3EyeL/lTrj5PWKb06ic5/6XYDr65rQ4uzQEGN70/6X5w==", "dev": true, "requires": { "irregular-plurals": "^2.0.0" @@ -11087,9 +11358,9 @@ "integrity": "sha512-VR1iadSfok+3MweY9HS2V5ExzwtS3TVvJyJUtdPc8K8dblg2m6H3YOKnlMdZlzp31AaGnM95GKhIFITo6j02OQ==" }, "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "progress": { "version": "2.0.3", @@ -11139,27 +11410,26 @@ "dev": true }, "proxy-addr": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", - "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.1.tgz", + "integrity": "sha1-x8Vm1etOP61n7rnHfFVYzMObiKg=", "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.8.0" + "ipaddr.js": "0.1.2" } }, "proxy-agent": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-2.3.1.tgz", - "integrity": "sha512-CNKuhC1jVtm8KJYFTS2ZRO71VCBx3QSA92So/e6NrY6GoJonkx3Irnk4047EsCcswczwqAekRj3s8qLRGahSKg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.1.0.tgz", + "integrity": "sha512-IkbZL4ClW3wwBL/ABFD2zJ8iP84CY0uKMvBPk/OceQe/cEjrxzN1pMHsLwhbzUoRhG9QbSxYC+Z7LBkTiBNvrA==", "requires": { "agent-base": "^4.2.0", "debug": "^3.1.0", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.1", "lru-cache": "^4.1.2", - "pac-proxy-agent": "^2.0.1", + "pac-proxy-agent": "^3.0.0", "proxy-from-env": "^1.0.0", - "socks-proxy-agent": "^3.0.0" + "socks-proxy-agent": "^4.0.1" }, "dependencies": { "debug": { @@ -11199,40 +11469,39 @@ "dev": true }, "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", + "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "puppeteer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.4.0.tgz", - "integrity": "sha512-WDnC1FSHTedvRSS8BZB73tPAx2svUCWFdcxVjrybw8pbKOAB1v5S/pW0EamkqQoL1mXiBc+v8lyYjhhzMHIk1Q==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.11.0.tgz", + "integrity": "sha512-iG4iMOHixc2EpzqRV+pv7o3GgmU2dNYEMkvKwSaQO/vMZURakwSOn/EYJ6OIRFYOque1qorzIBvrytPIQB3YzQ==", "dev": true, "requires": { - "debug": "^3.1.0", - "extract-zip": "^1.6.5", - "https-proxy-agent": "^2.1.0", + "debug": "^4.1.0", + "extract-zip": "^1.6.6", + "https-proxy-agent": "^2.2.1", "mime": "^2.0.3", - "progress": "^2.0.0", + "progress": "^2.0.1", "proxy-from-env": "^1.0.0", "rimraf": "^2.6.1", - "ws": "^3.0.0" + "ws": "^6.1.0" }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" @@ -11243,17 +11512,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true - }, - "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } } } }, @@ -11274,9 +11532,9 @@ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "querystringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz", - "integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" }, "quick-lru": { "version": "1.1.0", @@ -11300,9 +11558,9 @@ "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" }, "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.0.tgz", + "integrity": "sha1-pLJkz+C+XONqvjdlrJwqJIdG28A=" }, "range_check": { "version": "1.4.0", @@ -11342,11 +11600,6 @@ "strip-json-comments": "~2.0.1" }, "dependencies": { - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", @@ -11373,6 +11626,51 @@ "requires": { "find-up": "^2.0.0", "read-pkg": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } } }, "readable-stream": { @@ -11406,11 +11704,6 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -11449,14 +11742,6 @@ "resolve": "^1.1.6" } }, - "recursive-readdir": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", - "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", - "requires": { - "minimatch": "3.0.4" - } - }, "redback": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/redback/-/redback-0.5.1.tgz", @@ -11525,9 +11810,9 @@ "dev": true }, "registry-auth-token": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", - "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", "requires": { "rc": "^1.1.6", "safe-buffer": "^5.0.1" @@ -11626,12 +11911,6 @@ "tough-cookie": "^2.3.3" }, "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -11662,11 +11941,6 @@ "tough-cookie": "^2.3.3" }, "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -11685,9 +11959,9 @@ "dev": true }, "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, "requires-port": { @@ -11721,16 +11995,6 @@ } } }, - "resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - } - }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -11758,11 +12022,6 @@ "underscore": "~1.6" }, "dependencies": { - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, "underscore": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", @@ -11852,24 +12111,10 @@ "is-promise": "^2.1.0" } }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=" - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", - "requires": { - "rx-lite": "*" - } - }, "rxjs": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", - "dev": true, "requires": { "tslib": "^1.9.0" } @@ -11934,9 +12179,9 @@ } }, "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" }, "semver-diff": { "version": "2.1.0", @@ -11947,34 +12192,61 @@ } }, "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/send/-/send-0.8.3.tgz", + "integrity": "sha1-WTiGAE/LloobVyeBSjKziLO5kIM=", "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" + "debug": "1.0.4", + "depd": "0.4.4", + "destroy": "1.0.3", + "escape-html": "1.0.1", + "fresh": "0.2.2", + "mime": "1.2.11", + "ms": "0.6.2", + "on-finished": "2.1.0", + "range-parser": "~1.0.0" }, "dependencies": { + "debug": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", + "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=", + "requires": { + "ms": "0.6.2" + } + }, + "depd": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/depd/-/depd-0.4.4.tgz", + "integrity": "sha1-BwkfrnX5eCjYm0oCotR3jw58BmI=" + }, + "ee-first": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.0.5.tgz", + "integrity": "sha1-jJshKJjYzZ8alDZlDOe+ICyen/A=" + }, + "escape-html": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz", + "integrity": "sha1-GBoobq05ejmpKFfPsdQwUuNWv/A=" + }, "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=" }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=" + }, + "on-finished": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.1.0.tgz", + "integrity": "sha1-DFOfCSkej/rd4MiiWFD7LO3HAi0=", + "requires": { + "ee-first": "1.0.5" + } } } }, @@ -11984,13 +12256,6 @@ "integrity": "sha1-SCaXXZ8XPKOkFY6WmBYfdd7Hr+w=", "requires": { "fresh": "0.2.2" - }, - "dependencies": { - "fresh": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.2.2.tgz", - "integrity": "sha1-lzHc9WeMf660T7kDxPct9VGH+nc=" - } } }, "serve-index": { @@ -12001,40 +12266,83 @@ "accepts": "~1.0.7", "batch": "0.5.1", "parseurl": "~1.3.0" + } + }, + "serve-static": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.5.4.tgz", + "integrity": "sha1-gZ+zeuRr0C3VILd/z3/Y9REvl4I=", + "requires": { + "escape-html": "1.0.1", + "parseurl": "~1.3.0", + "send": "0.8.5", + "utils-merge": "1.0.0" }, "dependencies": { - "accepts": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.0.7.tgz", - "integrity": "sha1-W1AftPBwQwmWTM2wSBclQSCNqxo=", + "debug": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", + "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=", "requires": { - "mime-types": "~1.0.0", - "negotiator": "0.4.7" + "ms": "0.6.2" } }, - "mime-types": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", - "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=" + "depd": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/depd/-/depd-0.4.4.tgz", + "integrity": "sha1-BwkfrnX5eCjYm0oCotR3jw58BmI=" }, - "negotiator": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.7.tgz", - "integrity": "sha1-pBYPcXfsgGc4Yx0NMFIyXaQqvcg=" + "ee-first": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.0.5.tgz", + "integrity": "sha1-jJshKJjYzZ8alDZlDOe+ICyen/A=" + }, + "escape-html": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz", + "integrity": "sha1-GBoobq05ejmpKFfPsdQwUuNWv/A=" + }, + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=" + }, + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=" + }, + "on-finished": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.1.0.tgz", + "integrity": "sha1-DFOfCSkej/rd4MiiWFD7LO3HAi0=", + "requires": { + "ee-first": "1.0.5" + } + }, + "send": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/send/-/send-0.8.5.tgz", + "integrity": "sha1-N/cIIW5vUMF150xp/sU0hOL9gsc=", + "requires": { + "debug": "1.0.4", + "depd": "0.4.4", + "destroy": "1.0.3", + "escape-html": "1.0.1", + "fresh": "0.2.2", + "mime": "1.2.11", + "ms": "0.6.2", + "on-finished": "2.1.0", + "range-parser": "~1.0.0" + } + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" } } }, - "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" - } - }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -12170,6 +12478,12 @@ "requires": { "color-convert": "^1.9.0" } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true } } }, @@ -12186,9 +12500,9 @@ } }, "smart-buffer": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", - "integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.2.tgz", + "integrity": "sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw==" }, "snapdragon": { "version": "0.8.2", @@ -12220,11 +12534,6 @@ "requires": { "is-extendable": "^0.1.0" } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -12301,45 +12610,44 @@ } }, "snyk": { - "version": "1.143.1", - "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.143.1.tgz", - "integrity": "sha512-/9jL2G32toCfws5sYa2x8PHkm9r2R9uFqlExa/MYSQx1WP9Q32sTkT+f9FTzSlyk6HUkMg/g6lgPIQ0lRQ44ig==", + "version": "1.147.4", + "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.147.4.tgz", + "integrity": "sha512-siFpP8+DaX9KX5M10g3IjR7c68fC780PlVIT6uymi0ZgQkR3QvU2NiR+Y6v1hBF5JH9sT6RDXoZBXVAH/CfkSw==", "requires": { - "@snyk/dep-graph": "1.4.0", + "@snyk/dep-graph": "1.4.1", "@snyk/gemfile": "1.2.0", "abbrev": "^1.1.1", - "ansi-escapes": "^3.1.0", - "chalk": "^2.4.1", + "ansi-escapes": "^4.1.0", + "chalk": "^2.4.2", "configstore": "^3.1.2", "debug": "^3.1.0", "diff": "^4.0.1", - "get-uri": "2.0.2", - "inquirer": "^3.0.0", + "glob": "^7.1.3", + "inquirer": "^6.2.2", "lodash": "^4.17.11", "needle": "^2.2.4", - "opn": "^5.4.0", + "opn": "^5.5.0", "os-name": "^3.0.0", - "proxy-agent": "2.3.1", + "proxy-agent": "^3.1.0", "proxy-from-env": "^1.0.0", - "recursive-readdir": "^2.2.2", - "semver": "^5.6.0", - "snyk-config": "2.2.1", - "snyk-docker-plugin": "1.22.0", + "semver": "^6.0.0", + "snyk-config": "^2.2.1", + "snyk-docker-plugin": "1.22.1", "snyk-go-plugin": "1.6.1", - "snyk-gradle-plugin": "2.3.1", + "snyk-gradle-plugin": "2.5.0", "snyk-module": "1.9.1", "snyk-mvn-plugin": "2.0.1", - "snyk-nodejs-lockfile-parser": "1.11.0", - "snyk-nuget-plugin": "1.7.2", + "snyk-nodejs-lockfile-parser": "1.12.0", + "snyk-nuget-plugin": "1.9.0", "snyk-php-plugin": "1.5.2", - "snyk-policy": "1.13.3", + "snyk-policy": "1.13.4", "snyk-python-plugin": "1.9.1", "snyk-resolve": "1.0.1", "snyk-resolve-deps": "4.0.3", "snyk-sbt-plugin": "2.0.1", "snyk-tree": "^1.0.0", "snyk-try-require": "1.3.1", - "source-map-support": "^0.5.10", + "source-map-support": "^0.5.11", "tempfile": "^2.0.0", "then-fs": "^2.0.0", "update-notifier": "^2.5.0", @@ -12372,11 +12680,29 @@ "ms": "^2.1.1" } }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, + "semver": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", + "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==" + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -12413,9 +12739,9 @@ } }, "snyk-docker-plugin": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/snyk-docker-plugin/-/snyk-docker-plugin-1.22.0.tgz", - "integrity": "sha512-bykxNtfeWQNFjF6gv8u8w+TOa4fdr+teLm+DkvYlWkdlvaw5m4yywRI5USve4X6S9p4G+Fw4/wfjXx7LgCcxrQ==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/snyk-docker-plugin/-/snyk-docker-plugin-1.22.1.tgz", + "integrity": "sha512-4Qj+Fn9uCD7ydl60soVFfDG27ghZ6sCIiVPs5Wr62zgzbpnKCNF2MzXtxmsbZA1QRLKH2YxbJTog1Rvu013akA==", "requires": { "debug": "^3", "dockerfile-ast": "0.0.12", @@ -12449,11 +12775,14 @@ } }, "snyk-gradle-plugin": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/snyk-gradle-plugin/-/snyk-gradle-plugin-2.3.1.tgz", - "integrity": "sha512-546vvYw0dItj8hyamBO3tAGZSgUKMc/7gPnwv4vbfd6PgvX1S0+WlKM/hZ57iDJdMFbCWnbWjggRrBnvmw+jCA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/snyk-gradle-plugin/-/snyk-gradle-plugin-2.5.0.tgz", + "integrity": "sha512-XBXIYSeAaTye0mBdnr9TTHXfdebb5tUBUOuh6gCAcf3I/QMKPdWeMcKR1r+cEnMm+M4FGXonz+fpbxTHArX7Jg==", "requires": { - "clone-deep": "^0.3.0" + "clone-deep": "^0.3.0", + "tmp": "0.0.33", + "ts-node": "8.0.3", + "tslib": "^1.9.3" } }, "snyk-module": { @@ -12486,9 +12815,9 @@ "integrity": "sha512-TBrdcFXHdYuRYFCvpyUeFC+mCi6SOV3vdxgHrP7JRNnJwO8PYaKCObLJyhpRWa8IaHv/8CjJTmnEbWIh7BPHAA==" }, "snyk-nodejs-lockfile-parser": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.11.0.tgz", - "integrity": "sha512-eTdq5VcaHJwGoApejebTChi5hRcIDdNbO6lMwncS0zz9ZxXskoQ0C+VMdep8ELmJa0Gcz6es1sSkABPZs7frrg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.12.0.tgz", + "integrity": "sha512-HKLH30guWkRoLf4S5a39dz+wVdopeEI3dpxGjHzd8mbtxDKXY/8a8X9c+mkMrjmKxUA0UlbUwvRPCrJuVhvsZg==", "requires": { "@yarnpkg/lockfile": "^1.0.2", "graphlib": "^2.1.5", @@ -12499,9 +12828,9 @@ } }, "snyk-nuget-plugin": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/snyk-nuget-plugin/-/snyk-nuget-plugin-1.7.2.tgz", - "integrity": "sha512-zmYD9veH7OeIqGnZHiGv8c8mKtmYrxo2o7P4lNUkpHdCMMsar7moRJxGgO9WlcIrwAGjIhMdP9fUvJ+jVDEteQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/snyk-nuget-plugin/-/snyk-nuget-plugin-1.9.0.tgz", + "integrity": "sha512-GsxMcX2HpOFMv1oqAvI8b9dp4EKvOkvCixm24mwS4GjNo/ibeLAnsxs51W9qUyIaNF+F0y2J4k54n6azAnsxdw==", "requires": { "debug": "^3.1.0", "jszip": "^3.1.5", @@ -12558,15 +12887,15 @@ } }, "snyk-policy": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/snyk-policy/-/snyk-policy-1.13.3.tgz", - "integrity": "sha512-6J2a+Wt9zgvTtCwi4x8rLtkDQzFNPqubfIgs3aR35ZsEXPwI4XHGo0cxnJPDriqncp2JK72vnRpNfIZ7v0L1Mw==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/snyk-policy/-/snyk-policy-1.13.4.tgz", + "integrity": "sha512-b7KdS/q2uCbQZblf6OpdyBv+2V8l1yBkqSBd+ROTVi/+MT8MgfsnOFwayndEO6sQiUEUXmmKY4ocplk8GbJwYw==", "requires": { "debug": "^3.1.0", "email-validator": "^2.0.4", - "js-yaml": "^3.12.0", + "js-yaml": "^3.13.0", "lodash.clonedeep": "^4.5.0", - "semver": "^5.6.0", + "semver": "^6.0.0", "snyk-module": "^1.9.1", "snyk-resolve": "^1.0.1", "snyk-try-require": "^1.3.1", @@ -12585,6 +12914,11 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "semver": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", + "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==" } } }, @@ -12765,11 +13099,6 @@ "to-array": "0.1.3" }, "dependencies": { - "component-emitter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz", - "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=" - }, "debug": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", @@ -12802,13 +13131,6 @@ "requires": { "faye-websocket": "^0.10.0", "uuid": "^3.0.1" - }, - "dependencies": { - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - } } }, "sockjs-client-ws": { @@ -12830,21 +13152,21 @@ } }, "socks": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.10.tgz", - "integrity": "sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o=", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.2.tgz", + "integrity": "sha512-pCpjxQgOByDHLlNqlnh/mNSAxIUkyBBuwwhTcV+enZGbDaClPvHdvm6uvOwZfFJkam7cGhBNbb4JxiP8UZkRvQ==", "requires": { - "ip": "^1.1.4", - "smart-buffer": "^1.0.13" + "ip": "^1.1.5", + "smart-buffer": "4.0.2" } }, "socks-proxy-agent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz", - "integrity": "sha512-ZwEDymm204mTzvdqyUqOdovVr2YRd2NYskrYrF2LXyZ9qDiMAoFESGK8CRphiO7rtbo2Y757k2Nia3x2hGtalA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", + "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", "requires": { - "agent-base": "^4.1.0", - "socks": "^1.1.10" + "agent-base": "~4.2.1", + "socks": "~2.3.2" } }, "sort-keys": { @@ -12857,13 +13179,9 @@ } }, "source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", - "optional": true, - "requires": { - "amdefine": ">=0.0.4" - } + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-resolve": { "version": "0.5.2", @@ -12925,9 +13243,9 @@ } }, "spdx-license-ids": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", - "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", "dev": true }, "split-string": { @@ -12951,13 +13269,6 @@ "colors": "0.6.2", "frac": "0.3.1", "voc": "^1.1.0" - }, - "dependencies": { - "colors": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", - "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" - } } }, "sshpk": { @@ -13044,27 +13355,13 @@ "integrity": "sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=" }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string.prototype.codepointat": { @@ -13187,6 +13484,12 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -13228,17 +13531,6 @@ "mkdirp": "^0.5.1", "pump": "^1.0.0", "tar-stream": "^1.1.2" - }, - "dependencies": { - "pump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } } }, "tar-stream": { @@ -13269,11 +13561,6 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -13326,26 +13613,57 @@ "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", "requires": { "execa": "^0.7.0" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "the-argv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/the-argv/-/the-argv-1.0.0.tgz", - "integrity": "sha1-AIRwUAVzDdhNt1UlPJMa45jblSI=", - "dev": true - }, - "then-fs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/then-fs/-/then-fs-2.0.0.tgz", - "integrity": "sha1-cveS3Z0xcFqRrhnr/Piz+WjIHaI=", - "requires": { - "promise": ">=3.2 <8" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "the-argv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/the-argv/-/the-argv-1.0.0.tgz", + "integrity": "sha1-AIRwUAVzDdhNt1UlPJMa45jblSI=", + "dev": true + }, + "then-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/then-fs/-/then-fs-2.0.0.tgz", + "integrity": "sha1-cveS3Z0xcFqRrhnr/Piz+WjIHaI=", + "requires": { + "promise": ">=3.2 <8" } }, "thenify": { @@ -13471,9 +13789,9 @@ }, "dependencies": { "hoek": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.2.tgz", - "integrity": "sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==", "dev": true } } @@ -13485,6 +13803,17 @@ "dev": true, "requires": { "nopt": "~1.0.10" + }, + "dependencies": { + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + } } }, "tough-cookie": { @@ -13495,13 +13824,6 @@ "ip-regex": "^2.1.0", "psl": "^1.1.28", "punycode": "^2.1.1" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - } } }, "trim-newlines": { @@ -13516,6 +13838,25 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, + "ts-node": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.0.3.tgz", + "integrity": "sha512-2qayBA4vdtVRuDo11DEFSsD/SFsBXQBRZZhbRGSIkmYmVkWjULn/GGMdG10KVqkaGndljfaTD8dKjWgcejO8YA==", + "requires": { + "arg": "^4.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "^3.0.0" + }, + "dependencies": { + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + } + } + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", @@ -13545,6 +13886,11 @@ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, + "type-fest": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.0.tgz", + "integrity": "sha512-fg3sfdDdJDtdHLUpeGsf/fLyG1aapk6zgFiYG5+MDUPybGrJemH4SLk5tP7hGRe8ntxjg0q5LYW53b6YpJIQ9Q==" + }, "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", @@ -13595,6 +13941,11 @@ "requires": { "amdefine": ">=0.0.4" } + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" } } }, @@ -13604,11 +13955,12 @@ "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=" }, "uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-1.1.0.tgz", + "integrity": "sha1-WNbF2r+N+9jVKDSDmAbAP9YUMjI=", "requires": { - "random-bytes": "~1.0.0" + "base64-url": "1.2.1", + "native-or-bluebird": "~1.1.2" } }, "uid2": { @@ -13616,12 +13968,6 @@ "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=" }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", - "dev": true - }, "undefsafe": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", @@ -13733,9 +14079,9 @@ "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" }, "upath": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", "dev": true }, "update-notifier": { @@ -13789,13 +14135,6 @@ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "requires": { "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - } } }, "urix": { @@ -13841,21 +14180,6 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, - "uservoice-sso": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/uservoice-sso/-/uservoice-sso-0.1.0.tgz", - "integrity": "sha1-9cGvzj55Y4Ln7htmiK79dO09UB4=", - "requires": { - "underscore": "~1.5.2" - }, - "dependencies": { - "underscore": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.5.2.tgz", - "integrity": "sha1-EzXF5PXm0zu7SwBrqMhqAPVW3gg=" - } - } - }, "utf8": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.0.0.tgz", @@ -14055,6 +14379,11 @@ "sshpk": "^1.7.0" } }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, "request": { "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", @@ -14153,6 +14482,35 @@ "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", "requires": { "string-width": "^2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "window-size": { @@ -14161,45 +14519,17 @@ "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" }, "windows-release": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.1.0.tgz", - "integrity": "sha512-hBb7m7acFgQPQc222uEQTmdcGLeBmQLNLFIh0rDk3CwFOBrfjefLzEfEfmpMq8Af/n/GnFf3eYf203FY1PmudA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.2.0.tgz", + "integrity": "sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA==", "requires": { - "execa": "^0.10.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", - "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - } + "execa": "^1.0.0" } }, "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" }, "worker-loader": { "version": "2.0.0", @@ -14217,26 +14547,6 @@ "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } } }, "wrappy": { @@ -14298,6 +14608,12 @@ "requires": { "pify": "^3.0.0" } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true } } }, @@ -14332,15 +14648,15 @@ "requires": { "cfb": "~0.11.0", "codepage": "^1.14.0", - "commander": "^2.19.0", + "commander": "^2.20.0", "exit-on-epipe": "^1.0.1", "ssf": "~0.8.1" }, "dependencies": { "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" } } }, @@ -14363,35 +14679,19 @@ "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.8.1.tgz", "integrity": "sha1-8aAJ1SYdwnVGKLrLb7vw5uKr/6o=", "requires": { - "commander": "^2.19.0", + "commander": "^2.20.0", "concat-stream": "^2.0.0", "exit-on-epipe": "^1.0.1", "voc": "^1.1.0" }, "dependencies": { "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" } } }, - "colors": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", - "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" - }, - "concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, "frac": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/frac/-/frac-1.0.6.tgz", @@ -14400,16 +14700,6 @@ "voc": "^1.1.0" } }, - "readable-stream": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.2.0.tgz", - "integrity": "sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, "ssf": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.9.4.tgz", @@ -14419,14 +14709,6 @@ "frac": "~1.0.6", "voc": "^1.1.0" } - }, - "string_decoder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", - "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", - "requires": { - "safe-buffer": "~5.1.0" - } } } }, @@ -14452,9 +14734,9 @@ } }, "xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-12.0.0.tgz", + "integrity": "sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ==" }, "xmldom": { "version": "0.1.27", @@ -14540,6 +14822,15 @@ "get-stdin": "^6.0.0" } }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, "get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -14564,6 +14855,16 @@ "strip-bom": "^3.0.0" } }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, "map-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", @@ -14593,6 +14894,30 @@ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -14612,6 +14937,12 @@ "pify": "^3.0.0" } }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -14686,6 +15017,21 @@ "strip-eof": "^1.0.0" } }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, "graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", @@ -14704,12 +15050,46 @@ "strip-bom": "^3.0.0" } }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -14729,6 +15109,12 @@ "pify": "^3.0.0" } }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -14802,32 +15188,12 @@ "string-width": "^1.0.1", "window-size": "^0.1.4", "y18n": "^3.2.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } } }, "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -14835,9 +15201,9 @@ }, "dependencies": { "camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true } } @@ -14859,6 +15225,12 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "cliui": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", @@ -14870,51 +15242,11 @@ "wrap-ansi": "^2.0.0" } }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true }, "invert-kv": { "version": "2.0.0", @@ -14922,6 +15254,12 @@ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, "lcid": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", @@ -14931,16 +15269,6 @@ "invert-kv": "^2.0.0" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, "os-locale": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", @@ -14952,30 +15280,22 @@ "mem": "^4.0.0" } }, - "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" } }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", - "dev": true - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -15004,6 +15324,16 @@ "y18n": "^3.2.1 || ^4.0.0", "yargs-parser": "^11.1.1" } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, @@ -15027,6 +15357,11 @@ } } }, + "yn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.0.tgz", + "integrity": "sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==" + }, "youtube-api": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/youtube-api/-/youtube-api-2.0.10.tgz", @@ -15145,11 +15480,6 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-0.4.4.tgz", "integrity": "sha1-BwkfrnX5eCjYm0oCotR3jw58BmI=" }, - "destroy": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.3.tgz", - "integrity": "sha1-tDO0ck5x/YVR2YhRdIUcX8N34sk=" - }, "ee-first": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.0.5.tgz", @@ -15195,41 +15525,16 @@ "escape-html": "1.0.1" } }, - "fresh": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.2.2.tgz", - "integrity": "sha1-lzHc9WeMf660T7kDxPct9VGH+nc=" - }, "iconv-lite": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.4.tgz", "integrity": "sha1-6V8uQdsHNfwhZS94J6XuMuY8g6g=" }, - "ipaddr.js": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-0.1.2.tgz", - "integrity": "sha1-ah/T2FT1ACllw017vNm0qNSwRn4=" - }, "media-typer": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.2.0.tgz", "integrity": "sha1-2KBlITrf6qLnYyGitt2jb/YzWYQ=" }, - "merge-descriptors": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-0.0.2.tgz", - "integrity": "sha1-w2pSp4FDdRPFcnXzndnTF1FKyMc=" - }, - "methods": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.0.tgz", - "integrity": "sha1-XcpO4S31L/OwVhRZhqjwHLyGQ28=" - }, - "mime": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", - "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=" - }, "mime-types": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", @@ -15257,11 +15562,6 @@ "stream-counter": "~0.2.0" } }, - "node-uuid": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.1.tgz", - "integrity": "sha1-Oa71EOWImj3KnIlbUGxzquG6wEg=" - }, "on-finished": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.1.0.tgz", @@ -15270,24 +15570,11 @@ "ee-first": "1.0.5" } }, - "proxy-addr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.1.tgz", - "integrity": "sha1-x8Vm1etOP61n7rnHfFVYzMObiKg=", - "requires": { - "ipaddr.js": "0.1.2" - } - }, "qs": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/qs/-/qs-2.2.2.tgz", "integrity": "sha1-3+eD8YVLGsKzreknda0D4n4DIYw=" }, - "range-parser": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.0.tgz", - "integrity": "sha1-pLJkz+C+XONqvjdlrJwqJIdG28A=" - }, "raw-body": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.3.0.tgz", @@ -15297,51 +15584,6 @@ "iconv-lite": "0.4.4" } }, - "send": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/send/-/send-0.8.3.tgz", - "integrity": "sha1-WTiGAE/LloobVyeBSjKziLO5kIM=", - "requires": { - "debug": "1.0.4", - "depd": "0.4.4", - "destroy": "1.0.3", - "escape-html": "1.0.1", - "fresh": "0.2.2", - "mime": "1.2.11", - "ms": "0.6.2", - "on-finished": "2.1.0", - "range-parser": "~1.0.0" - } - }, - "serve-static": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.5.4.tgz", - "integrity": "sha1-gZ+zeuRr0C3VILd/z3/Y9REvl4I=", - "requires": { - "escape-html": "1.0.1", - "parseurl": "~1.3.0", - "send": "0.8.5", - "utils-merge": "1.0.0" - }, - "dependencies": { - "send": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/send/-/send-0.8.5.tgz", - "integrity": "sha1-N/cIIW5vUMF150xp/sU0hOL9gsc=", - "requires": { - "debug": "1.0.4", - "depd": "0.4.4", - "destroy": "1.0.3", - "escape-html": "1.0.1", - "fresh": "0.2.2", - "mime": "1.2.11", - "ms": "0.6.2", - "on-finished": "2.1.0", - "range-parser": "~1.0.0" - } - } - } - }, "type-is": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.3.2.tgz", @@ -15351,11 +15593,6 @@ "mime-types": "~1.0.1" } }, - "utils-merge": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", - "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" - }, "vary": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/vary/-/vary-0.1.0.tgz", From 68d091bf5d1c8fc68bed6416d7560cb0f819fbe8 Mon Sep 17 00:00:00 2001 From: Miguel Laginha Date: Fri, 19 Apr 2019 20:23:26 +0100 Subject: [PATCH 21/21] refactor: move to es6 modules --- packages/oae-activity/config/activity.js | 24 +- .../oae-authentication/config/strategies.js | 424 ++++++++++-------- packages/oae-authz/lib/invitations/model.js | 4 +- packages/oae-config/lib/api.js | 11 +- packages/oae-config/lib/migration.js | 2 +- packages/oae-content/lib/api.js | 5 +- packages/oae-content/lib/init.js | 28 +- .../emailTemplates/test_shared.shared.js | 5 +- packages/oae-email/lib/api.js | 2 +- packages/oae-email/lib/test/util.js | 2 +- packages/oae-folders/lib/init.js | 20 +- packages/oae-jitsi/lib/init.js | 15 +- packages/oae-logger/lib/api.js | 1 - packages/oae-principals/lib/api.js | 7 +- packages/oae-principals/lib/init.js | 32 +- .../oae-search/lib/schema/resourceSchema.js | 22 +- packages/oae-tests/lib/util.js | 2 +- packages/oae-tests/runner/before-tests.js | 3 +- packages/oae-ui/config/skin.js | 24 +- packages/oae-util/lib/cassandra.js | 8 +- packages/oae-util/lib/oae.js | 4 - 21 files changed, 345 insertions(+), 300 deletions(-) diff --git a/packages/oae-activity/config/activity.js b/packages/oae-activity/config/activity.js index 1715b8f426..dc69e12e8e 100644 --- a/packages/oae-activity/config/activity.js +++ b/packages/oae-activity/config/activity.js @@ -15,18 +15,16 @@ import { Bool } from 'oae-config/lib/fields'; -module.exports = { - title: 'OAE Activity Module', - activity: { - name: 'Activity Configuration', - description: 'Core Configuration', - elements: { - enabled: new Bool( - 'Activity Posting Enabled', - 'When disabled, no actions originating from the tenant will trigger an activity', - true, - { tenantOverride: false, suppress: true } - ) - } +export const title = 'OAE Activity Module'; +export const activity = { + name: 'Activity Configuration', + description: 'Core Configuration', + elements: { + enabled: new Bool( + 'Activity Posting Enabled', + 'When disabled, no actions originating from the tenant will trigger an activity', + true, + { tenantOverride: false, suppress: true } + ) } }; diff --git a/packages/oae-authentication/config/strategies.js b/packages/oae-authentication/config/strategies.js index f88b435b0f..675e384484 100644 --- a/packages/oae-authentication/config/strategies.js +++ b/packages/oae-authentication/config/strategies.js @@ -15,200 +15,242 @@ import { Bool, Text } from 'oae-config/lib/fields'; -module.exports = { - title: 'OAE Authentication Module', - local: { - name: 'Local Authentication', - description: 'Allow local authentication for tenant', - elements: { - allowAccountCreation: new Bool('Local Account Creation', 'Allow users to create their own account', true), - enabled: new Bool('Local Authentication Enabled', 'Allow local authentication for tenant', true) - } - }, - google: { - name: 'Google Authentication', - description: 'Allow Google authentication for tenant', - elements: { - enabled: new Bool('Google Authentication Enabled', 'Allow Google authentication for tenant', false), - key: new Text('Google client ID', 'Google client ID', process.env.GOOGLE_CLIENT_ID, { - suppress: true - }), - secret: new Text('Google client secret', 'Google client secret', process.env.GOOGLE_CLIENT_SECRET, { - suppress: true - }), - domains: new Text('Google domain(s)', 'A comma-separated list of allowed email domains (optional)', '') - } - }, - twitter: { - name: 'Twitter Authentication', - description: 'Allow Twitter authentication for tenant', - elements: { - enabled: new Bool('Twitter Authentication Enabled', 'Allow Twitter authentication for tenant', true), - key: new Text('Twitter consumer key', 'Twitter consumer key', process.env.TWITTER_KEY, { - suppress: true - }), - secret: new Text('Twitter consumer secret', 'Twitter consumer secret', process.env.TWITTER_SECRET, { - suppress: true - }) - } - }, - facebook: { - name: 'Facebook Authentication', - description: 'Allow Facebook authentication for tenant', - elements: { - enabled: new Bool('Facebook Authentication Enabled', 'Allow Facebook authentication for tenant', false), - appid: new Text('Facebook App ID', 'Facebook App ID', process.env.FACEBOOK_APP_ID, { - suppress: true - }), - secret: new Text('Secret', 'Secret', process.env.FACEBOOK_APP_SECRET, { +export const title = 'OAE Authentication Module'; +export const local = { + name: 'Local Authentication', + description: 'Allow local authentication for tenant', + elements: { + allowAccountCreation: new Bool( + 'Local Account Creation', + 'Allow users to create their own account', + true + ), + enabled: new Bool('Local Authentication Enabled', 'Allow local authentication for tenant', true) + } +}; +export const google = { + name: 'Google Authentication', + description: 'Allow Google authentication for tenant', + elements: { + enabled: new Bool( + 'Google Authentication Enabled', + 'Allow Google authentication for tenant', + false + ), + key: new Text('Google client ID', 'Google client ID', process.env.GOOGLE_CLIENT_ID, { + suppress: true + }), + secret: new Text( + 'Google client secret', + 'Google client secret', + process.env.GOOGLE_CLIENT_SECRET, + { suppress: true - }) - } - }, - shibboleth: { - name: 'Shibboleth Authentication', - description: 'Allow Shibboleth authentication for tenant', - elements: { - enabled: new Bool('Shibboleth Authentication Enabled', 'Allow Shibboleth authentication for tenant', false), - name: new Text('Name', 'A name that users will recognize as their identity provider', ''), - idpEntityID: new Text('Identity Provider entity ID', 'The entity ID of the IdP', '', { + } + ), + domains: new Text( + 'Google domain(s)', + 'A comma-separated list of allowed email domains (optional)', + '' + ) + } +}; +export const twitter = { + name: 'Twitter Authentication', + description: 'Allow Twitter authentication for tenant', + elements: { + enabled: new Bool( + 'Twitter Authentication Enabled', + 'Allow Twitter authentication for tenant', + true + ), + key: new Text('Twitter consumer key', 'Twitter consumer key', process.env.TWITTER_KEY, { + suppress: true + }), + secret: new Text( + 'Twitter consumer secret', + 'Twitter consumer secret', + process.env.TWITTER_SECRET, + { suppress: true - }), - externalIdAttributes: new Text( - 'External ID Attribute', - 'The attribute that uniquely identifies the user. This should be a prioritised space seperated list', - 'persistent-id targeted-id eppn', - { suppress: true } - ), - mapDisplayName: new Text( - 'Display name', - 'The attibute(s) that should be used to construct the displayname. This should be a prioritised space seperated list. e.g., `displayname cn`', - 'displayname cn', - { suppress: true } - ), - mapEmail: new Text( - 'Email', - 'The attibute(s) that should be used to construct the email. This should be a prioritised space seperated list. e.g., `mail email eppn`', - 'mail email eppn', - { suppress: true } - ), - mapLocale: new Text( - 'Locale', - 'The attibute(s) that should be used to construct the locale. This should be a prioritised space seperated list. e.g., `locality locale`', - 'locality locale', - { suppress: true } - ) - } - }, - cas: { - name: 'CAS Authentication', - description: 'Allow CAS authentication for tenant', - elements: { - enabled: new Bool('CAS Authentication Enabled', 'Allow CAS authentication for tenant', false), - name: new Text('Name', 'A name that users will recognize as their identity provider', ''), - url: new Text( - 'Host', - 'The URL at which the CAS server can be reached. This should include http(s)://, any non-standard port and any base path with no trailing slash', - '', - { suppress: true } - ), - loginPath: new Text( - 'Login Path', - 'The path to which the user should be redirected to start the authentication flow', - '/login', - { suppress: true } - ), - useSaml: new Bool( - 'Use SAML', - 'Use SAML to get CAS attributes. When using this, you probably need to set the Validate Path to "/samlValidate"', - false, - { suppress: true } - ), - validatePath: new Text( - 'CAS Validate Path', - 'The CAS validation path such as /serviceValdiate', - '/serviceValidate', - { suppress: true } - ), - logoutUrl: new Text( - 'Logout URL', - 'The URL to which the user should be redirected when logging out of OAE. This should be a full url including a valid protocol (e.g., https://my.cas.server/cas/logout)', - '', - { suppress: true } - ), - mapDisplayName: new Text( - 'Display name', - 'The attibute(s) that should be used to construct the displayname. e.g., {first_name} {last_name}', - '', - { suppress: true } - ), - mapEmail: new Text('Email', 'The attibute(s) that should be used to construct the email. e.g., {mail}', '', { + } + ) + } +}; +export const facebook = { + name: 'Facebook Authentication', + description: 'Allow Facebook authentication for tenant', + elements: { + enabled: new Bool( + 'Facebook Authentication Enabled', + 'Allow Facebook authentication for tenant', + false + ), + appid: new Text('Facebook App ID', 'Facebook App ID', process.env.FACEBOOK_APP_ID, { + suppress: true + }), + secret: new Text('Secret', 'Secret', process.env.FACEBOOK_APP_SECRET, { + suppress: true + }) + } +}; +export const shibboleth = { + name: 'Shibboleth Authentication', + description: 'Allow Shibboleth authentication for tenant', + elements: { + enabled: new Bool( + 'Shibboleth Authentication Enabled', + 'Allow Shibboleth authentication for tenant', + false + ), + name: new Text('Name', 'A name that users will recognize as their identity provider', ''), + idpEntityID: new Text('Identity Provider entity ID', 'The entity ID of the IdP', '', { + suppress: true + }), + externalIdAttributes: new Text( + 'External ID Attribute', + 'The attribute that uniquely identifies the user. This should be a prioritised space seperated list', + 'persistent-id targeted-id eppn', + { suppress: true } + ), + mapDisplayName: new Text( + 'Display name', + 'The attibute(s) that should be used to construct the displayname. This should be a prioritised space seperated list. e.g., `displayname cn`', + 'displayname cn', + { suppress: true } + ), + mapEmail: new Text( + 'Email', + 'The attibute(s) that should be used to construct the email. This should be a prioritised space seperated list. e.g., `mail email eppn`', + 'mail email eppn', + { suppress: true } + ), + mapLocale: new Text( + 'Locale', + 'The attibute(s) that should be used to construct the locale. This should be a prioritised space seperated list. e.g., `locality locale`', + 'locality locale', + { suppress: true } + ) + } +}; +export const cas = { + name: 'CAS Authentication', + description: 'Allow CAS authentication for tenant', + elements: { + enabled: new Bool('CAS Authentication Enabled', 'Allow CAS authentication for tenant', false), + name: new Text('Name', 'A name that users will recognize as their identity provider', ''), + url: new Text( + 'Host', + 'The URL at which the CAS server can be reached. This should include http(s)://, any non-standard port and any base path with no trailing slash', + '', + { suppress: true } + ), + loginPath: new Text( + 'Login Path', + 'The path to which the user should be redirected to start the authentication flow', + '/login', + { suppress: true } + ), + useSaml: new Bool( + 'Use SAML', + 'Use SAML to get CAS attributes. When using this, you probably need to set the Validate Path to "/samlValidate"', + false, + { suppress: true } + ), + validatePath: new Text( + 'CAS Validate Path', + 'The CAS validation path such as /serviceValdiate', + '/serviceValidate', + { suppress: true } + ), + logoutUrl: new Text( + 'Logout URL', + 'The URL to which the user should be redirected when logging out of OAE. This should be a full url including a valid protocol (e.g., https://my.cas.server/cas/logout)', + '', + { suppress: true } + ), + mapDisplayName: new Text( + 'Display name', + 'The attibute(s) that should be used to construct the displayname. e.g., {first_name} {last_name}', + '', + { suppress: true } + ), + mapEmail: new Text( + 'Email', + 'The attibute(s) that should be used to construct the email. e.g., {mail}', + '', + { suppress: true - }), - mapLocale: new Text('Locale', 'The attibute(s) that should be used to construct the locale. e.g., {locale}', '', { + } + ), + mapLocale: new Text( + 'Locale', + 'The attibute(s) that should be used to construct the locale. e.g., {locale}', + '', + { suppress: true - }) - } - }, - ldap: { - name: 'LDAP Authentication', - description: 'Allow LDAP authentication for tenant', - elements: { - enabled: new Bool('LDAP Authentication Enabled', 'Allow LDAP authentication for tenant', false), - url: new Text( - 'Host', - 'The URL at which the LDAP server can be reached. This should include both the protocol and the port. E.g. `ldaps://lookup.example.com:636` (required)', - '', - { suppress: true } - ), - adminDn: new Text( - 'Admin Distinguished Name', - 'The DN that identifies an admin user that can search for user information. E.g. uid=admin,ou=users,dc=example,dc=com (required)', - '', - { suppress: true } - ), - adminPassword: new Text( - 'Admin password', - 'The password for the admin DN that can be used to bind to LDAP. (required)', - '', - { suppress: true } - ), - searchBase: new Text( - 'Base', - 'The base DN under which to search for users. E.g. ou=users,dc=example,dc=com (required)', - '', - { suppress: true } - ), - searchFilter: new Text( - 'Filter', - 'The LDAP search filter with which to find a user by username, e.g. (uid={{username}}). Use the literal `{{username}}` to have the given username be interpolated in for the LDAP search. (required)', - '', - { suppress: true } - ), - mapExternalId: new Text( - 'LDAP External ID field', - 'The name of the LDAP field that contains an identifier that uniquely identifies the user in LDAP (required)', - 'uid', - { suppress: true } - ), - mapDisplayName: new Text( - 'LDAP DisplayName field', - "The name of the LDAP field that contains the user's displayName (required)", - 'cn', - { suppress: true } - ), - mapEmail: new Text( - 'LDAP Email field', - "The name of the LDAP field that contains the user's email address (optional)", - '', - { suppress: true } - ), - mapLocale: new Text( - 'LDAP Locale field', - "The name of the LDAP field that contains the user's locale (optional)", - '', - { suppress: true } - ) - } + } + ) + } +}; +export const ldap = { + name: 'LDAP Authentication', + description: 'Allow LDAP authentication for tenant', + elements: { + enabled: new Bool('LDAP Authentication Enabled', 'Allow LDAP authentication for tenant', false), + url: new Text( + 'Host', + 'The URL at which the LDAP server can be reached. This should include both the protocol and the port. E.g. `ldaps://lookup.example.com:636` (required)', + '', + { suppress: true } + ), + adminDn: new Text( + 'Admin Distinguished Name', + 'The DN that identifies an admin user that can search for user information. E.g. uid=admin,ou=users,dc=example,dc=com (required)', + '', + { suppress: true } + ), + adminPassword: new Text( + 'Admin password', + 'The password for the admin DN that can be used to bind to LDAP. (required)', + '', + { suppress: true } + ), + searchBase: new Text( + 'Base', + 'The base DN under which to search for users. E.g. ou=users,dc=example,dc=com (required)', + '', + { suppress: true } + ), + searchFilter: new Text( + 'Filter', + 'The LDAP search filter with which to find a user by username, e.g. (uid={{username}}). Use the literal `{{username}}` to have the given username be interpolated in for the LDAP search. (required)', + '', + { suppress: true } + ), + mapExternalId: new Text( + 'LDAP External ID field', + 'The name of the LDAP field that contains an identifier that uniquely identifies the user in LDAP (required)', + 'uid', + { suppress: true } + ), + mapDisplayName: new Text( + 'LDAP DisplayName field', + "The name of the LDAP field that contains the user's displayName (required)", + 'cn', + { suppress: true } + ), + mapEmail: new Text( + 'LDAP Email field', + "The name of the LDAP field that contains the user's email address (optional)", + '', + { suppress: true } + ), + mapLocale: new Text( + 'LDAP Locale field', + "The name of the LDAP field that contains the user's locale (optional)", + '', + { suppress: true } + ) } }; diff --git a/packages/oae-authz/lib/invitations/model.js b/packages/oae-authz/lib/invitations/model.js index 57728a2640..dd3f7164a6 100644 --- a/packages/oae-authz/lib/invitations/model.js +++ b/packages/oae-authz/lib/invitations/model.js @@ -42,6 +42,4 @@ Invitation.fromHash = function(hash, resource, inviterUser) { return new Invitation(resource, hash.email, inviterUser, hash.role); }; -module.exports = { - Invitation -}; +export { Invitation }; diff --git a/packages/oae-config/lib/api.js b/packages/oae-config/lib/api.js index c8167e31b1..7fb2f3986f 100644 --- a/packages/oae-config/lib/api.js +++ b/packages/oae-config/lib/api.js @@ -17,6 +17,8 @@ import util from 'util'; import _ from 'underscore'; import clone from 'clone'; +import * as Modules from 'oae-util/lib/modules'; +import * as Cassandra from 'oae-util/lib/cassandra'; import * as EmitterAPI from 'oae-emitter'; import * as IO from 'oae-util/lib/io'; import * as OaeUtil from 'oae-util/lib/util'; @@ -307,8 +309,6 @@ const updateTenantConfig = function(tenantAlias, callback) { */ const _cacheSchema = function(callback) { // Get the available module - // This is bad but not my fault - const Modules = require('oae-util/lib/modules'); const modules = Modules.getAvailableModules(); const toDo = modules.length; let done = 0; @@ -411,7 +411,6 @@ const _cacheAllTenantConfigs = function(callback) { * @param {Object} callback.tenantConfigs An object keyed by tenant alias, whose value is a key value pair of `configKey->value` for each stored configuration value for the tenant */ const _getAllPersistentTenantConfigs = function(callback) { - const Cassandra = require('oae-util/lib/cassandra'); Cassandra.runAutoPagedQuery( 'SELECT "tenantAlias", "configKey", "value", WRITETIME("value") FROM "Config"', null, @@ -435,7 +434,6 @@ const _getAllPersistentTenantConfigs = function(callback) { * @param {Object} callback.tenantConfig An object keyed by configKey whose value is the value set for the tenant */ const _getPersistentTenantConfig = function(alias, callback) { - const Cassandra = require('oae-util/lib/cassandra'); Cassandra.runQuery( 'SELECT "tenantAlias", "configKey", "value", WRITETIME("value") FROM "Config" WHERE "tenantAlias" = ?', [alias], @@ -461,7 +459,6 @@ const _rowsToConfig = function(rows) { const persistentConfig = {}; _.each(rows, row => { - const Cassandra = require('oae-util/lib/cassandra'); const hash = Cassandra.rowToHash(row); const key = hash.configKey; let { value } = hash; @@ -669,8 +666,6 @@ const updateConfig = function(ctx, tenantAlias, configValues, callback) { // Indicate that configuration is about to be updated eventEmitter.emit('preUpdate', tenantAlias); - // TODO this is crazy bad - const Cassandra = require('oae-util/lib/cassandra'); // Perform all the config field updates Cassandra.runBatchQuery(queries, err => { if (err) { @@ -792,8 +787,6 @@ const clearConfig = function(ctx, tenantAlias, configFields, callback) { // Indicate that config values are about to be cleared eventEmitter.emit('preClear', tenantAlias); - // TODO this is crazy bad - const Cassandra = require('oae-util/lib/cassandra'); Cassandra.runBatchQuery(queries, err => { if (err) { return callback(err); diff --git a/packages/oae-config/lib/migration.js b/packages/oae-config/lib/migration.js index 39cde42999..0042874a4b 100644 --- a/packages/oae-config/lib/migration.js +++ b/packages/oae-config/lib/migration.js @@ -1,4 +1,4 @@ -const Cassandra = require('oae-util/lib/cassandra'); +import * as Cassandra from 'oae-util/lib/cassandra'; /** * Ensure that the config schema is created. diff --git a/packages/oae-content/lib/api.js b/packages/oae-content/lib/api.js index edb80d0c0b..7e94760d2e 100644 --- a/packages/oae-content/lib/api.js +++ b/packages/oae-content/lib/api.js @@ -30,6 +30,7 @@ import * as EmitterAPI from 'oae-emitter'; import * as LibraryAPI from 'oae-library'; import { logger } from 'oae-logger'; +import { getFoldersByIds } from 'oae-folders/lib/internal/dao'; import * as MessageBoxAPI from 'oae-messagebox'; import * as OaeUtil from 'oae-util/lib/util'; import * as PrincipalsDAO from 'oae-principals/lib/internal/dao'; @@ -781,9 +782,7 @@ const canManageFolders = function(ctx, folderIds, callback) { return callback(validator.getFirstError()); } - // Ensure that all the folders exist. We have to require the FoldersDAO - // inline as we'd get a dependency cycle otherwise - require('oae-folders/lib/internal/dao').getFoldersByIds(folderIds, (err, folders) => { + getFoldersByIds(folderIds, (err, folders) => { if (err) { return callback(err); } diff --git a/packages/oae-content/lib/init.js b/packages/oae-content/lib/init.js index fc6d941edc..4205f87a8f 100644 --- a/packages/oae-content/lib/init.js +++ b/packages/oae-content/lib/init.js @@ -26,25 +26,25 @@ import * as ContentAPI from './api'; import { ContentConstants } from './constants'; import * as ContentSearch from './search'; -const log = logger('oae-content'); +// Initialize the content library capabilities +// eslint-disable-next-line no-unused-vars +import * as library from './library'; -export function init(config, callback) { - // Initialize the content library capabilities - // eslint-disable-next-line import/no-unassigned-import - require('./library'); +// Initialize activity capabilities +// eslint-disable-next-line no-unused-vars +import * as activity from './activity'; - // Initialize activity capabilities - // eslint-disable-next-line import/no-unassigned-import - require('./activity'); +// Ensure that the preview listeners get registered +// eslint-disable-next-line no-unused-vars +import * as previews from './previews'; - // Ensure that the preview listeners get registered - // eslint-disable-next-line import/no-unassigned-import - require('./previews'); +// Initialize invitations listeners +// eslint-disable-next-line no-unused-vars +import * as invitations from './invitations'; - // Initialize invitations listeners - // eslint-disable-next-line import/no-unassigned-import - require('./invitations'); +const log = logger('oae-content'); +export function init(config, callback) { // Initialize the etherpad client. Etherpad.refreshConfiguration(config.etherpad); diff --git a/packages/oae-email/emailTemplates/test_shared.shared.js b/packages/oae-email/emailTemplates/test_shared.shared.js index a7eb9798c4..cefa66213f 100644 --- a/packages/oae-email/emailTemplates/test_shared.shared.js +++ b/packages/oae-email/emailTemplates/test_shared.shared.js @@ -33,7 +33,4 @@ const getBar = function() { return 'bar'; }; -module.exports = { - getFoo, - getBar -}; +export { getFoo, getBar }; diff --git a/packages/oae-email/lib/api.js b/packages/oae-email/lib/api.js index 6b3f56a670..78adcf9025 100755 --- a/packages/oae-email/lib/api.js +++ b/packages/oae-email/lib/api.js @@ -38,13 +38,13 @@ import * as UIAPI from 'oae-ui'; import { htmlToText } from 'nodemailer-html-to-text'; import { MailParser } from 'mailparser'; import { Validator } from 'oae-util/lib/validator'; +import * as TenantsAPI from 'oae-tenants'; const EmailConfig = setUpConfig('oae-email'); const log = logger('oae-email'); const Telemetry = telemetry('oae-email'); -const TenantsAPI = require('oae-tenants'); const TenantsConfig = setUpConfig('oae-tenants'); diff --git a/packages/oae-email/lib/test/util.js b/packages/oae-email/lib/test/util.js index 4471f3a1f6..b88c7230df 100644 --- a/packages/oae-email/lib/test/util.js +++ b/packages/oae-email/lib/test/util.js @@ -24,7 +24,7 @@ import * as Cassandra from 'oae-util/lib/cassandra'; import * as MqTestsUtil from 'oae-util/lib/test/mq-util'; import * as EmailAPI from 'oae-email'; -const { ActivityConstants } = require('oae-activity/lib/constants'); +import { ActivityConstants } from 'oae-activity/lib/constants'; /** * Send and return a single email message. This helper utility will ensure that the activity / notifications queue diff --git a/packages/oae-folders/lib/init.js b/packages/oae-folders/lib/init.js index 73345769d8..43ad8aacc3 100644 --- a/packages/oae-folders/lib/init.js +++ b/packages/oae-folders/lib/init.js @@ -15,16 +15,16 @@ import * as FoldersSearch from './search'; -export function init(config, callback) { - // Register activity, library, previews and search functionality - // eslint-disable-next-line no-unused-vars - const activity = require('./activity'); - // eslint-disable-next-line no-unused-vars - const library = require('./library'); - // eslint-disable-next-line no-unused-vars - const previews = require('./previews'); - // eslint-disable-next-line no-unused-vars - const invitations = require('./invitations'); +// Register activity, library, previews and search functionality +// eslint-disable-next-line no-unused-vars +import * as activity from './activity'; +// eslint-disable-next-line no-unused-vars +import * as library from './library'; +// eslint-disable-next-line no-unused-vars +import * as previews from './previews'; +// eslint-disable-next-line no-unused-vars +import * as invitations from './invitations'; +export function init(config, callback) { return FoldersSearch.init(callback); } diff --git a/packages/oae-jitsi/lib/init.js b/packages/oae-jitsi/lib/init.js index bc559b3994..dc7573c0c4 100644 --- a/packages/oae-jitsi/lib/init.js +++ b/packages/oae-jitsi/lib/init.js @@ -16,19 +16,18 @@ import { logger } from 'oae-logger'; import * as MeetingSearch from './search'; +// Register the activity functionality +// eslint-disable-next-line no-unused-vars +import * as activity from './activity'; + +// Register the library functionality +// eslint-disable-next-line no-unused-vars +import * as library from './library'; const log = logger('oae-jitsi-init'); export function init(config, callback) { log().info('Initializing the oae-jitsi module'); - // Register the activity functionality - // eslint-disable-next-line no-unused-vars - const activity = require('./activity'); - - // Register the library functionality - // eslint-disable-next-line no-unused-vars - const library = require('./library'); - return MeetingSearch.init(callback); } diff --git a/packages/oae-logger/lib/api.js b/packages/oae-logger/lib/api.js index f7d3a2faef..cc3ec9e85e 100644 --- a/packages/oae-logger/lib/api.js +++ b/packages/oae-logger/lib/api.js @@ -127,7 +127,6 @@ const _wrapErrorFunction = function(loggerName, errorFunction) { * Keep track of the error count with the telemetry API before handing control back to Bunyan */ const wrapperErrorFunction = function(...args) { - // The telemetry API needs to be required inline as there would otherwise be a cyclical dependency const Telemetry = require('oae-telemetry').telemetry('logger'); // Increase the general error count that keeps track of the number of errors throughout the application diff --git a/packages/oae-principals/lib/api.js b/packages/oae-principals/lib/api.js index 24f476f86c..026aa3f107 100644 --- a/packages/oae-principals/lib/api.js +++ b/packages/oae-principals/lib/api.js @@ -41,10 +41,7 @@ import * as UserAPI from './api.user'; import PrincipalsAPI from './internal/emitter'; const allExports = {}; -export default _.extend(allExports, GroupAPI, PictureAPI, TermsAndConditionsAPI, UserAPI); - -export { PrincipalsAPI as emitter }; - // This file would become unmaintainable if all the logic would be placed here. // That's why we split them up in a couple of files of which the api logic gets exported. -// _.extend(module.exports, GroupAPI, PictureAPI, TermsAndConditionsAPI, UserAPI); +export default _.extend(allExports, GroupAPI, PictureAPI, TermsAndConditionsAPI, UserAPI); +export { PrincipalsAPI as emitter }; diff --git a/packages/oae-principals/lib/init.js b/packages/oae-principals/lib/init.js index 122335f2fc..c3d4a742c7 100644 --- a/packages/oae-principals/lib/init.js +++ b/packages/oae-principals/lib/init.js @@ -22,22 +22,30 @@ import { AuthzConstants } from 'oae-authz/lib/constants'; import { Context } from 'oae-context'; import { User } from 'oae-principals/lib/model'; -export function init(config, callback) { - // Initialize activity capabilities - require('oae-principals/lib/activity'); // eslint-disable-line import/no-unassigned-import +// Initialize activity capabilities +// eslint-disable-line no-ununsed-vars +import * as activity from 'oae-principals/lib/activity'; + +// Initialize search capabilities +// eslint-disable-line no-ununsed-vars +import * as search from 'oae-principals/lib/search'; - // Initialize search capabilities - require('oae-principals/lib/search'); // eslint-disable-line import/no-unassigned-import +// Initialize invitations capabilities +// eslint-disable-line no-ununsed-vars +import * as invitations from 'oae-principals/lib/invitations'; - // Initialize invitations capabilities - require('oae-principals/lib/invitations'); // eslint-disable-line import/no-unassigned-import +// Initialize members and memberships library capabilities +// eslint-disable-line no-ununsed-vars +import * as members from 'oae-principals/lib/libraries/members'; - // Initialize members and memberships library capabilities - require('oae-principals/lib/libraries/members'); // eslint-disable-line import/no-unassigned-import - require('oae-principals/lib/libraries/memberships'); // eslint-disable-line import/no-unassigned-import - // Initialize principals delete capabilities - require('oae-principals/lib/delete'); // eslint-disable-line import/no-unassigned-import +// eslint-disable-line no-ununsed-vars +import * as memberships from 'oae-principals/lib/libraries/memberships'; +// Initialize principals delete capabilities +// eslint-disable-line no-ununsed-vars +import * as deleted from 'oae-principals/lib/delete'; + +export function init(config, callback) { return _ensureGlobalAdmin(config, callback); } diff --git a/packages/oae-search/lib/schema/resourceSchema.js b/packages/oae-search/lib/schema/resourceSchema.js index 8a5393a4ee..6c7f05e99f 100644 --- a/packages/oae-search/lib/schema/resourceSchema.js +++ b/packages/oae-search/lib/schema/resourceSchema.js @@ -54,7 +54,7 @@ * {String} schema.created Id of the user who created the item. This is used to limit to items created by current user only. */ /* eslint-disable unicorn/filename-case, camelcase */ -module.exports = { +const schema = { id: { type: 'string', store: 'yes', @@ -149,3 +149,23 @@ module.exports = { index: 'not_analyzed' } }; +export let { + id, + tenantAlias, + resourceType, + resourceSubType, + thumbnailUrl, + displayName, + description, + email, + _extra, + visibility, + joinable, + deleted, + q_high, + q_low, + sort, + dateCreated, + lastModified, + createdBy +} = schema; diff --git a/packages/oae-tests/lib/util.js b/packages/oae-tests/lib/util.js index 9f3eb544c0..e9c8e11df3 100644 --- a/packages/oae-tests/lib/util.js +++ b/packages/oae-tests/lib/util.js @@ -21,6 +21,7 @@ import util from 'util'; import async from 'async'; import _ from 'underscore'; +import { config } from '../../../config'; import bodyParser from 'body-parser'; import clone from 'clone'; import express from 'express'; @@ -1132,7 +1133,6 @@ const _ensureAuthenticated = function(restCtx, callback) { const createInitialTestConfig = function() { // Require the configuration file, from here on the configuration should be // passed around instead of required - let { config } = require('../../../config'); const envConfig = require('../../../' + (process.env.NODE_ENV || 'local')).config; config = _.extend({}, config, envConfig); diff --git a/packages/oae-tests/runner/before-tests.js b/packages/oae-tests/runner/before-tests.js index ceef1b921a..4a866d8172 100644 --- a/packages/oae-tests/runner/before-tests.js +++ b/packages/oae-tests/runner/before-tests.js @@ -15,6 +15,7 @@ import * as TestsUtil from 'oae-tests/lib/util'; import { logger } from 'oae-logger'; +import nock from 'nock'; const log = logger('before-tests'); @@ -52,8 +53,6 @@ beforeEach(function(callback) { }); afterEach(function(callback) { - const nock = require('nock'); - // Ensure we don't mess with the HTTP stack by accident nock.enableNetConnect(); diff --git a/packages/oae-ui/config/skin.js b/packages/oae-ui/config/skin.js index 3bd562750d..b5e7bea6cf 100644 --- a/packages/oae-ui/config/skin.js +++ b/packages/oae-ui/config/skin.js @@ -15,18 +15,16 @@ import { Text } from 'oae-config/lib/fields'; -module.exports = { - title: 'OAE UI Module', - skin: { - name: 'Skin settings', - description: 'Define the skin settings', - elements: { - variables: new Text( - 'JSON Variables', - 'A JSON dictionary that holds the less variables. Each key is a less variable', - {}, - { tenantOverride: true, suppress: true, globalAdminOnly: false } - ) - } +export const title = 'OAE UI Module'; +export const skin = { + name: 'Skin settings', + description: 'Define the skin settings', + elements: { + variables: new Text( + 'JSON Variables', + 'A JSON dictionary that holds the less variables. Each key is a less variable', + {}, + { tenantOverride: true, suppress: true, globalAdminOnly: false } + ) } }; diff --git a/packages/oae-util/lib/cassandra.js b/packages/oae-util/lib/cassandra.js index 3902ed8d79..8a58f474d3 100644 --- a/packages/oae-util/lib/cassandra.js +++ b/packages/oae-util/lib/cassandra.js @@ -26,9 +26,8 @@ import * as OaeUtil from 'oae-util/lib/util'; const { Row, dataTypes } = cassandra.types; const _ = require('underscore'); -const log = logger('oae-cassandra'); - -const Telemetry = telemetry('cassandra'); +let log = null; +let Telemetry = null; const DEFAULT_ITERATEALL_BATCH_SIZE = 100; let CONFIG = null; @@ -45,6 +44,9 @@ const init = function(config, callback) { callback = callback || function() {}; CONFIG = config; + log = logger('oae-cassandra'); + + Telemetry = telemetry('cassandra'); const { keyspace } = CONFIG; CONFIG.keyspace = 'system'; client = _createNewClient(CONFIG.hosts, CONFIG.keyspace); diff --git a/packages/oae-util/lib/oae.js b/packages/oae-util/lib/oae.js index 7308435108..0635da2980 100644 --- a/packages/oae-util/lib/oae.js +++ b/packages/oae-util/lib/oae.js @@ -74,13 +74,9 @@ const init = function(config, callback) { // Start up the global and tenant servers globalAdminServer = Server.setupServer(config.servers.globalAdminPort, config); - module.exports.globalAdminServer = globalAdminServer; tenantServer = Server.setupServer(config.servers.tenantPort, config); - module.exports.tenantServer = tenantServer; tenantRouter = Server.setupRouter(tenantServer); - module.exports.tenantRouter = tenantRouter; globalAdminRouter = Server.setupRouter(globalAdminServer); - module.exports.globalAdminRouter = globalAdminRouter; // Initialize the modules and their CFs, as well as registering the Rest endpoints Modules.bootstrapModules(config, err => {