Adds AAD auth to laboratory and CLI
This commit is contained in:
Родитель
244416de22
Коммит
ea12c1c5f2
|
@ -30,3 +30,10 @@ AZURE_CLIENT_ID=00000000-0000-0000-0000-000000000000
|
|||
# Functional test variables
|
||||
TEST_QUEUE_SERVICE_URL=https://mystorage.queue.core.windows.net
|
||||
TEST_BASE_VOLUME_PATH=/tmp/sds/volumes/
|
||||
|
||||
# Laboratory AAD auth
|
||||
# If AUTH_MODE is not set, auth is disabled
|
||||
AUTH_MODE=aad
|
||||
AUTH_TENANT_ID=00000000-0000-0000-0000-000000000000
|
||||
AUTH_LABORATORY_CLIENT_ID=00000000-0000-0000-0000-000000000000
|
||||
AUTH_CLI_CLIENT_ID=00000000-0000-0000-0000-000000000000
|
||||
|
|
|
@ -152,6 +152,54 @@
|
|||
"adal-node": "^0.1.28"
|
||||
}
|
||||
},
|
||||
"@azure/msal-common": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-1.0.0.tgz",
|
||||
"integrity": "sha512-l/+1Z9kQWLAlMwJ/c3MGhy4ujtEAK/3CMUaUXHvjUIsQknLFRb9+b3id5YSuToPfAvdUkAQGDZiQXosv1I+eLA==",
|
||||
"requires": {
|
||||
"debug": "^4.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@azure/msal-node": {
|
||||
"version": "1.0.0-alpha.3",
|
||||
"resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.0.0-alpha.3.tgz",
|
||||
"integrity": "sha512-DVV0VqCscFxjLjg5+DH0RXzToEdtUx7syl/C35ePuBDVeSHQ/mmD3v0yacTg7MN4biU1qYnv/xDunLLi5pWCVA==",
|
||||
"requires": {
|
||||
"@azure/msal-common": "^1.0.0",
|
||||
"axios": "^0.19.2",
|
||||
"debug": "^4.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@azure/storage-queue": {
|
||||
"version": "12.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-queue/-/storage-queue-12.0.4.tgz",
|
||||
|
@ -446,6 +494,25 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@types/passport": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.4.tgz",
|
||||
"integrity": "sha512-h5OfAbfBBYSzjeU0GTuuqYEk9McTgWeGQql9g3gUw2/NNCfD7VgExVRYJVVeU13Twn202Mvk9BT0bUrl30sEgA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"@types/passport-azure-ad": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport-azure-ad/-/passport-azure-ad-4.0.7.tgz",
|
||||
"integrity": "sha512-5O0OspIhlOyQA20kZoEW+8/8zmFa1StkzshZ/+QDapcmfb3AJb3N9wUq3trpWRHwpXUSyZMsqTvc7bYtBIRcPg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express": "*",
|
||||
"@types/passport": "*"
|
||||
}
|
||||
},
|
||||
"@types/qs": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz",
|
||||
|
@ -814,6 +881,16 @@
|
|||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"asn1.js": {
|
||||
"version": "4.10.1",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
|
||||
"integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
|
||||
"requires": {
|
||||
"bn.js": "^4.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
|
@ -863,6 +940,11 @@
|
|||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
||||
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
||||
},
|
||||
"base64url": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
|
||||
"integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A=="
|
||||
},
|
||||
"bcp47": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bcp47/-/bcp47-1.1.2.tgz",
|
||||
|
@ -914,6 +996,11 @@
|
|||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "4.11.9",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
|
@ -1009,6 +1096,11 @@
|
|||
"fill-range": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"brorand": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
|
||||
},
|
||||
"browser-stdout": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
|
||||
|
@ -1046,11 +1138,48 @@
|
|||
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
|
||||
"dev": true
|
||||
},
|
||||
"bunyan": {
|
||||
"version": "1.8.14",
|
||||
"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.14.tgz",
|
||||
"integrity": "sha512-LlahJUxXzZLuw/hetUQJmRgZ1LF6+cr5TPpRj6jf327AsiIq2jhYEH4oqUUkVKTor+9w2BT3oxVwhzE5lw9tcg==",
|
||||
"requires": {
|
||||
"dtrace-provider": "~0.8",
|
||||
"moment": "^2.19.3",
|
||||
"mv": "~2",
|
||||
"safe-json-stringify": "~1"
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
|
||||
},
|
||||
"cache-manager": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-2.11.1.tgz",
|
||||
"integrity": "sha512-XhUuc9eYwkzpK89iNewFwtvcDYMUsvtwzHeyEOPJna/WsVsXcrzsA1ft2M0QqPNunEzLhNCYPo05tEfG+YuNow==",
|
||||
"requires": {
|
||||
"async": "1.5.2",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"lru-cache": "4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.0.tgz",
|
||||
"integrity": "sha1-tcvwFVbBaWb+vlTO7A+03JDfbCg=",
|
||||
"requires": {
|
||||
"pseudomap": "^1.0.1",
|
||||
"yallist": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cacheable-request": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz",
|
||||
|
@ -1759,6 +1888,15 @@
|
|||
"resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz",
|
||||
"integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg=="
|
||||
},
|
||||
"dtrace-provider": {
|
||||
"version": "0.8.8",
|
||||
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz",
|
||||
"integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"nan": "^2.14.0"
|
||||
}
|
||||
},
|
||||
"duplexer3": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
|
||||
|
@ -1792,6 +1930,20 @@
|
|||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz",
|
||||
"integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA=="
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||
"requires": {
|
||||
"bn.js": "^4.4.0",
|
||||
"brorand": "^1.0.1",
|
||||
"hash.js": "^1.0.0",
|
||||
"hmac-drbg": "^1.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
"minimalistic-crypto-utils": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
|
@ -2425,12 +2577,31 @@
|
|||
"integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==",
|
||||
"dev": true
|
||||
},
|
||||
"hash.js": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
|
||||
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"dev": true
|
||||
},
|
||||
"hmac-drbg": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
|
||||
"requires": {
|
||||
"hash.js": "^1.0.3",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"hosted-git-info": {
|
||||
"version": "2.8.6",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.6.tgz",
|
||||
|
@ -2851,6 +3022,16 @@
|
|||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"jwk-to-pem": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-1.2.6.tgz",
|
||||
"integrity": "sha1-1QfOzkAInFJI4J7GgmaiAwqcYyU=",
|
||||
"requires": {
|
||||
"asn1.js": "^4.5.2",
|
||||
"elliptic": "^6.2.3",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"jws": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
||||
|
@ -2913,6 +3094,11 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
|
||||
},
|
||||
"lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
|
||||
},
|
||||
"lodash.get": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||
|
@ -3063,6 +3249,16 @@
|
|||
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
|
||||
"dev": true
|
||||
},
|
||||
"minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
||||
},
|
||||
"minimalistic-crypto-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
|
||||
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
|
@ -3313,6 +3509,41 @@
|
|||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
|
||||
"dev": true
|
||||
},
|
||||
"mv": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
|
||||
"integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"mkdirp": "~0.5.1",
|
||||
"ncp": "~2.0.0",
|
||||
"rimraf": "~2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
|
||||
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "2 || 3",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
|
||||
"integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"glob": "^6.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
|
||||
|
@ -3326,8 +3557,7 @@
|
|||
"ncp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
|
||||
"integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=",
|
||||
"dev": true
|
||||
"integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M="
|
||||
},
|
||||
"needle": {
|
||||
"version": "2.4.1",
|
||||
|
@ -3584,6 +3814,11 @@
|
|||
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
||||
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
|
||||
},
|
||||
"oauth": {
|
||||
"version": "0.9.14",
|
||||
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.14.tgz",
|
||||
"integrity": "sha1-xXSIg6QLU94wrenKvyEAQUuKCXE="
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||
|
@ -3760,6 +3995,45 @@
|
|||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||
},
|
||||
"passport": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/passport/-/passport-0.3.2.tgz",
|
||||
"integrity": "sha1-ndAJ+RXo/glbASSgG4+C2gdRAQI=",
|
||||
"requires": {
|
||||
"passport-strategy": "1.x.x",
|
||||
"pause": "0.0.1"
|
||||
}
|
||||
},
|
||||
"passport-azure-ad": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/passport-azure-ad/-/passport-azure-ad-4.2.1.tgz",
|
||||
"integrity": "sha512-pyaGhuvxHTUu/jrCCBOtR3GoSC12+u7B/iEQVK7z+JdDQZE/I+3oMgN1Ls4umnb5TfPuVyM76kvjqwB9kAjBgw==",
|
||||
"requires": {
|
||||
"async": "^1.5.2",
|
||||
"base64url": "^3.0.0",
|
||||
"bunyan": "^1.8.0",
|
||||
"cache-manager": "^2.0.0",
|
||||
"jwk-to-pem": "^1.2.6",
|
||||
"jws": "^3.1.3",
|
||||
"lodash": "^4.11.2",
|
||||
"oauth": "0.9.14",
|
||||
"passport": "^0.3.2",
|
||||
"request": "^2.72.0",
|
||||
"valid-url": "^1.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
|
||||
}
|
||||
}
|
||||
},
|
||||
"passport-strategy": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
|
||||
"integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ="
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
||||
|
@ -3812,6 +4086,11 @@
|
|||
"integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
|
||||
"dev": true
|
||||
},
|
||||
"pause": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
||||
"integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
|
||||
},
|
||||
"performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
|
@ -3869,8 +4148,7 @@
|
|||
"pseudomap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
|
||||
"dev": true
|
||||
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
|
||||
},
|
||||
"psl": {
|
||||
"version": "1.7.0",
|
||||
|
@ -4196,6 +4474,12 @@
|
|||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
|
||||
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
|
||||
},
|
||||
"safe-json-stringify": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz",
|
||||
"integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==",
|
||||
"optional": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
|
@ -5505,6 +5789,11 @@
|
|||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
},
|
||||
"valid-url": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz",
|
||||
"integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA="
|
||||
},
|
||||
"validate-npm-package-license": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||
|
@ -5739,8 +6028,7 @@
|
|||
"yallist": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
||||
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
|
||||
"dev": true
|
||||
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
|
||||
},
|
||||
"yamljs": {
|
||||
"version": "0.3.0",
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@azure/identity": "^1.0.3",
|
||||
"@azure/msal-node": "^1.0.0-alpha.3",
|
||||
"@azure/storage-queue": "^12.0.4",
|
||||
"axios": "^0.19.2",
|
||||
"body-parser": "^1.19.0",
|
||||
|
@ -40,6 +41,7 @@
|
|||
"io-ts": "^2.2.2",
|
||||
"js-yaml": "^3.13.1",
|
||||
"luxon": "^1.24.1",
|
||||
"passport-azure-ad": "^4.2.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sequelize": "^5.21.7",
|
||||
"sequelize-typescript": "^1.1.0",
|
||||
|
@ -64,6 +66,7 @@
|
|||
"@types/mocha": "^7.0.2",
|
||||
"@types/nock": "^11.1.0",
|
||||
"@types/node": "^12.12.37",
|
||||
"@types/passport-azure-ad": "^4.0.7",
|
||||
"@types/request-promise": "^4.1.46",
|
||||
"@types/sinon": "^7.5.2",
|
||||
"@types/sinonjs__fake-timers": "^6.0.1",
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
import * as msal from '@azure/msal-node';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as yaml from 'js-yaml';
|
||||
import * as t from 'io-ts';
|
||||
import * as url from 'url';
|
||||
|
||||
import {
|
||||
IllegalOperationError,
|
||||
LaboratoryClient,
|
||||
validate,
|
||||
ClientConnectionInfoType,
|
||||
} from '../laboratory';
|
||||
import { lab } from '../../test/unit/laboratory/shared';
|
||||
|
||||
// global client
|
||||
let client: LaboratoryClient | undefined;
|
||||
|
||||
const configDir = path.join(os.homedir(), '.sds');
|
||||
const connFilePath = 'sds.yaml';
|
||||
const tokenCachePath = 'accessTokens.json';
|
||||
|
||||
const cachePlugin = {
|
||||
async readFromStorage() {
|
||||
return readConfig(tokenCachePath);
|
||||
},
|
||||
async writeToStorage(getMergedState: (oldState: string) => string) {
|
||||
let oldFile = '';
|
||||
try {
|
||||
oldFile = await readConfig(tokenCachePath);
|
||||
} finally {
|
||||
const mergedState = getMergedState(oldFile);
|
||||
await writeConfig(tokenCachePath, mergedState);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export async function initConnection(host: string) {
|
||||
const labUrl = url.parse(host);
|
||||
const endpoint = labUrl.href;
|
||||
|
||||
const connectionInfo = await new LaboratoryClient(
|
||||
endpoint
|
||||
).negotiateConnection();
|
||||
const config: IConnectConfiguration = {
|
||||
endpoint,
|
||||
...connectionInfo,
|
||||
};
|
||||
await writeConfig(connFilePath, yaml.safeDump(config));
|
||||
const newClient = buildClient(config);
|
||||
await newClient.validateConnection();
|
||||
client = newClient;
|
||||
}
|
||||
|
||||
export async function getLabClient(): Promise<LaboratoryClient> {
|
||||
try {
|
||||
if (client) {
|
||||
return client;
|
||||
}
|
||||
|
||||
const text = await readConfig(connFilePath);
|
||||
const config = validate(ConnectConfigurationType, yaml.safeLoad(text));
|
||||
client = buildClient(config);
|
||||
return client;
|
||||
} catch {
|
||||
throw new IllegalOperationError(
|
||||
'No laboratory connection. Use the "connect" command to specify a laboratory.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
const ConnectConfigurationType = t.intersection([
|
||||
t.type({
|
||||
endpoint: t.string,
|
||||
}),
|
||||
ClientConnectionInfoType,
|
||||
t.partial({
|
||||
// from msal-common/AccountInfo
|
||||
account: t.type({
|
||||
homeAccountId: t.string,
|
||||
environment: t.string,
|
||||
tenantId: t.string,
|
||||
username: t.string,
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
type IConnectConfiguration = t.TypeOf<typeof ConnectConfigurationType>;
|
||||
|
||||
function acquireAADAccessToken(config: IConnectConfiguration) {
|
||||
if (config.type !== 'aad') {
|
||||
throw new Error(
|
||||
'Cannot retrieve an AAD access token for a non-AAD connection'
|
||||
);
|
||||
}
|
||||
|
||||
return async () => {
|
||||
const pca = new msal.PublicClientApplication({
|
||||
auth: {
|
||||
clientId: config.clientId,
|
||||
authority: config.authority,
|
||||
},
|
||||
cache: {
|
||||
cachePlugin,
|
||||
},
|
||||
});
|
||||
const cache = pca.getTokenCache();
|
||||
|
||||
try {
|
||||
await cache.readFromPersistence();
|
||||
const silentResult = await pca.acquireTokenSilent({
|
||||
account: config.account!,
|
||||
scopes: config.scopes,
|
||||
});
|
||||
cache.writeToPersistence();
|
||||
return silentResult.accessToken;
|
||||
} catch (e) {
|
||||
const deviceCodeResult = await pca.acquireTokenByDeviceCode({
|
||||
deviceCodeCallback: response => console.log(response.message),
|
||||
scopes: config.scopes,
|
||||
});
|
||||
config.account = deviceCodeResult.account;
|
||||
await writeConfig(connFilePath, yaml.safeDump(config));
|
||||
cache.writeToPersistence();
|
||||
return deviceCodeResult.accessToken;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function buildClient(config: IConnectConfiguration): LaboratoryClient {
|
||||
const tokenRetriever =
|
||||
config.type === 'aad' ? acquireAADAccessToken(config) : undefined;
|
||||
return new LaboratoryClient(config.endpoint, tokenRetriever);
|
||||
}
|
||||
|
||||
async function readConfig(filePath: string): Promise<string> {
|
||||
const fullPath = path.join(configDir, filePath);
|
||||
try {
|
||||
return await fs.readFile(fullPath, 'utf8');
|
||||
} catch (e) {
|
||||
const err = e as NodeJS.ErrnoException;
|
||||
if (err.code === 'ENOENT') {
|
||||
return '';
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async function writeConfig(filePath: string, data: string): Promise<void> {
|
||||
const fullPath = path.join(configDir, filePath);
|
||||
await fs.mkdir(configDir, { recursive: true });
|
||||
await fs.writeFile(fullPath, data);
|
||||
}
|
102
src/cli/main.ts
102
src/cli/main.ts
|
@ -1,12 +1,9 @@
|
|||
import { Command } from 'commander';
|
||||
import * as fs from 'fs';
|
||||
import { Decoder } from 'io-ts';
|
||||
import * as t from 'io-ts';
|
||||
import * as yaml from 'js-yaml';
|
||||
import { DateTime } from 'luxon';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
|
||||
import {
|
||||
BenchmarkType,
|
||||
|
@ -17,11 +14,11 @@ import {
|
|||
IllegalOperationError,
|
||||
IRun,
|
||||
ISuite,
|
||||
LaboratoryClient,
|
||||
SuiteType,
|
||||
validate,
|
||||
} from '../laboratory';
|
||||
|
||||
import { initConnection, getLabClient } from './conn';
|
||||
import { decodeError } from './decode_error';
|
||||
import { configureDemo } from './demo';
|
||||
import { formatChoices, formatTable, Alignment } from './formatting';
|
||||
|
@ -149,21 +146,11 @@ function examples(argv: string[]) {
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
async function connect(host: string) {
|
||||
if (host === undefined) {
|
||||
if (connection) {
|
||||
console.log(`Connected to ${connection!.endpoint}.`);
|
||||
} else {
|
||||
console.log(
|
||||
'No laboratory connection. Use the "connect" command to specify a laboratory.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const labUrl = url.parse(host);
|
||||
const endpoint = labUrl.href;
|
||||
const config = yaml.safeDump({ endpoint });
|
||||
fs.writeFileSync(sdsFile, config);
|
||||
tryInitializeConnection();
|
||||
console.log(`Connected to ${endpoint}.`);
|
||||
throw new Error('You must specify a host.');
|
||||
}
|
||||
|
||||
await initConnection(host);
|
||||
console.log(`Connected to ${host}.`);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -189,7 +176,7 @@ async function createHelper<T>(ops: ISpecOps<T>, specFile: string) {
|
|||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
async function demo() {
|
||||
configureDemo(getLab());
|
||||
configureDemo(await getLabClient());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -265,7 +252,7 @@ async function listHelper<T extends IEntityBase>(
|
|||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
async function results(benchmark: string, suite: string) {
|
||||
const results = await getLab().allRunResults(benchmark, suite);
|
||||
const results = await (await getLabClient()).allRunResults(benchmark, suite);
|
||||
|
||||
if (results.length === 0) {
|
||||
console.log(`No matching results`);
|
||||
|
@ -320,7 +307,10 @@ async function results(benchmark: string, suite: string) {
|
|||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
async function run(candidate: string, suite: string) {
|
||||
const run = await getLab().createRunRequest({ candidate, suite });
|
||||
const run = await (await getLabClient()).createRunRequest({
|
||||
candidate,
|
||||
suite,
|
||||
});
|
||||
console.log(`Scheduling run ${run.name}`);
|
||||
}
|
||||
|
||||
|
@ -371,29 +361,30 @@ const benchmarkOps: ISpecOps<IBenchmark> = {
|
|||
name: () => 'benchmark',
|
||||
load: (specFile: string) => load(BenchmarkType, specFile),
|
||||
format: (spec: IBenchmark) => formatSpec(spec),
|
||||
all: () => getLab().allBenchmarks(),
|
||||
one: (name: string) => getLab().oneBenchmark(name),
|
||||
upsert: (spec: IBenchmark, name?: string) =>
|
||||
getLab().upsertBenchmark(spec, name),
|
||||
all: async () => (await getLabClient()).allBenchmarks(),
|
||||
one: async (name: string) => (await getLabClient()).oneBenchmark(name),
|
||||
upsert: async (spec: IBenchmark, name?: string) =>
|
||||
(await getLabClient()).upsertBenchmark(spec, name),
|
||||
};
|
||||
|
||||
const candidateOps: ISpecOps<ICandidate> = {
|
||||
name: () => 'candidate',
|
||||
load: (specFile: string) => load(CandidateType, specFile),
|
||||
format: (spec: ICandidate) => formatSpec(spec),
|
||||
all: () => getLab().allCandidates(),
|
||||
one: (name: string) => getLab().oneCandidate(name),
|
||||
upsert: (spec: ICandidate, name?: string) =>
|
||||
getLab().upsertCandidate(spec, name),
|
||||
all: async () => (await getLabClient()).allCandidates(),
|
||||
one: async (name: string) => (await getLabClient()).oneCandidate(name),
|
||||
upsert: async (spec: ICandidate, name?: string) =>
|
||||
(await getLabClient()).upsertCandidate(spec, name),
|
||||
};
|
||||
|
||||
const suiteOps: ISpecOps<ISuite> = {
|
||||
name: () => 'suite',
|
||||
load: (specFile: string) => load(SuiteType, specFile),
|
||||
format: (spec: ISuite) => formatSpec(spec),
|
||||
all: () => getLab().allSuites(),
|
||||
one: (name: string) => getLab().oneSuite(name),
|
||||
upsert: (spec: ISuite, name?: string) => getLab().upsertSuite(spec, name),
|
||||
all: async () => (await getLabClient()).allSuites(),
|
||||
one: async (name: string) => (await getLabClient()).oneSuite(name),
|
||||
upsert: async (spec: ISuite, name?: string) =>
|
||||
(await getLabClient()).upsertSuite(spec, name),
|
||||
};
|
||||
|
||||
const runOps: ISpecOps<IRun> = {
|
||||
|
@ -402,8 +393,8 @@ const runOps: ISpecOps<IRun> = {
|
|||
throw new IllegalOperationError(`Load operation not supported for IRun.`);
|
||||
},
|
||||
format: (spec: IRun) => formatSpec(spec),
|
||||
all: () => getLab().allRuns(),
|
||||
one: (name: string) => getLab().oneRun(name),
|
||||
all: async () => (await getLabClient()).allRuns(),
|
||||
one: async (name: string) => (await getLabClient()).oneRun(name),
|
||||
upsert: (spec: IRun, name?: string) => {
|
||||
throw new IllegalOperationError(`Upsert operation not supported for IRun.`);
|
||||
},
|
||||
|
@ -455,47 +446,4 @@ function formatSpec(spec: object) {
|
|||
return yaml.safeDump(spec, {});
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Connection management
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
const ConnectConfigurationType = t.type({
|
||||
endpoint: t.string,
|
||||
});
|
||||
export type IConnectConfiguration = t.TypeOf<typeof ConnectConfigurationType>;
|
||||
|
||||
const sdsFile = path.join(os.homedir(), '.sds');
|
||||
let connection: IConnectConfiguration | undefined;
|
||||
let lab: LaboratoryClient | undefined;
|
||||
|
||||
function getLab(): LaboratoryClient {
|
||||
if (connection === undefined || lab === undefined) {
|
||||
tryInitializeConnection();
|
||||
}
|
||||
if (connection === undefined || lab === undefined) {
|
||||
const message =
|
||||
'No laboratory connection. Use the "connect" command to specify a laboratory.';
|
||||
throw new IllegalOperationError(message);
|
||||
}
|
||||
return lab;
|
||||
}
|
||||
|
||||
function tryInitializeConnection() {
|
||||
try {
|
||||
const yamlText = fs.readFileSync(sdsFile, 'utf8');
|
||||
const root = yaml.safeLoad(yamlText);
|
||||
connection = validate(ConnectConfigurationType, root);
|
||||
lab = new LaboratoryClient(connection.endpoint);
|
||||
} catch (e) {
|
||||
const err = e as NodeJS.ErrnoException;
|
||||
if (err.code !== 'ENOENT') {
|
||||
const message = `Invalid ~/.sds file: "${err.message}"`;
|
||||
console.log(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main(process.argv);
|
||||
|
|
|
@ -20,6 +20,14 @@ import {
|
|||
AzureStorageQueueConfiguration,
|
||||
} from './queue';
|
||||
|
||||
import {
|
||||
AADConfiguration,
|
||||
AuthConfiguration,
|
||||
LaboratoryConfiguration,
|
||||
AuthMode,
|
||||
NoAuthConfiguration,
|
||||
} from './laboratory/server/configuration';
|
||||
|
||||
class AzureCredential {
|
||||
private static instance: TokenCredential;
|
||||
|
||||
|
@ -114,7 +122,45 @@ export function ParseDatabaseConfiguration(): DatabaseConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
export function ParseLaboratoryConfiguration() {
|
||||
function ParseAuthConfiguration(): AuthConfiguration {
|
||||
const authMode = env.get('AUTH_MODE').asString();
|
||||
|
||||
if (authMode === 'aad') {
|
||||
const tenantId = env
|
||||
.get('AUTH_TENANT_ID')
|
||||
.required()
|
||||
.asString();
|
||||
const laboratoryClientId = env
|
||||
.get('AUTH_LABORATORY_CLIENT_ID')
|
||||
.required()
|
||||
.asString();
|
||||
const cliClientId = env
|
||||
.get('AUTH_CLI_CLIENT_ID')
|
||||
.required()
|
||||
.asString();
|
||||
const scopes = env
|
||||
.get('AUTH_SCOPES')
|
||||
.default('laboratory')
|
||||
.asArray(' ')
|
||||
.map(s => `api://${laboratoryClientId}/${s}`);
|
||||
|
||||
// offline_access is required to use refresh tokens
|
||||
scopes.push('offline_access');
|
||||
|
||||
const config: AADConfiguration = {
|
||||
mode: AuthMode.AAD,
|
||||
tenantId,
|
||||
laboratoryClientId,
|
||||
cliClientId,
|
||||
scopes,
|
||||
};
|
||||
return config;
|
||||
} else {
|
||||
return NoAuthConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
export function ParseLaboratoryConfiguration(): LaboratoryConfiguration {
|
||||
const port = env
|
||||
.get('PORT')
|
||||
.default(3000)
|
||||
|
@ -138,5 +184,6 @@ export function ParseLaboratoryConfiguration() {
|
|||
port,
|
||||
queue: ParseQueueConfiguration(),
|
||||
database: ParseDatabaseConfiguration(),
|
||||
auth: ParseAuthConfiguration(),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ import {
|
|||
BenchmarkType,
|
||||
CandidateArrayType,
|
||||
CandidateType,
|
||||
ClientConnectionInfoType,
|
||||
IBenchmark,
|
||||
ICandidate,
|
||||
IClientConnectionInfo,
|
||||
ILaboratory,
|
||||
IllegalOperationError,
|
||||
IReportRunResults,
|
||||
|
@ -28,15 +30,48 @@ import {
|
|||
validate,
|
||||
} from '../logic';
|
||||
|
||||
const config: AxiosRequestConfig = {
|
||||
// TODO: put credentials here.
|
||||
};
|
||||
// A TokenRetriever should return a valid OAuth2 Bearer token
|
||||
type TokenRetriever = () => Promise<string>;
|
||||
|
||||
export class LaboratoryClient implements ILaboratory {
|
||||
endpoint: string;
|
||||
tokenRetriever?: TokenRetriever;
|
||||
|
||||
constructor(endpoint: string) {
|
||||
constructor(endpoint: string, tokenRetriever?: TokenRetriever) {
|
||||
this.endpoint = endpoint;
|
||||
this.tokenRetriever = tokenRetriever;
|
||||
}
|
||||
|
||||
private async getConfig(): Promise<AxiosRequestConfig> {
|
||||
if (this.tokenRetriever) {
|
||||
return {
|
||||
headers: {
|
||||
Authorization: `Bearer ${await this.tokenRetriever()}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Connection info
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
async negotiateConnection(): Promise<IClientConnectionInfo> {
|
||||
const url = new URL('connect', this.endpoint);
|
||||
const response = await axios.get(url.toString());
|
||||
const connectionInfo = validate(ClientConnectionInfoType, response.data);
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
async validateConnection(): Promise<void> {
|
||||
const url = new URL('connect/validate', this.endpoint);
|
||||
const response = await axios.get(url.toString(), await this.getConfig());
|
||||
if (response.status !== 200) {
|
||||
throw new Error('There was something wrong with the connection');
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -46,7 +81,7 @@ export class LaboratoryClient implements ILaboratory {
|
|||
/////////////////////////////////////////////////////////////////////////////
|
||||
async allBenchmarks(): Promise<IBenchmark[]> {
|
||||
const url = new URL('benchmarks', this.endpoint);
|
||||
const response = await axios.get(url.toString(), config);
|
||||
const response = await axios.get(url.toString(), await this.getConfig());
|
||||
const benchmarks = validate(BenchmarkArrayType, response.data);
|
||||
return benchmarks;
|
||||
}
|
||||
|
@ -54,7 +89,7 @@ export class LaboratoryClient implements ILaboratory {
|
|||
async oneBenchmark(rawName: string): Promise<IBenchmark> {
|
||||
const name = normalizeName(rawName);
|
||||
const url = new URL(`benchmarks/${name}`, this.endpoint);
|
||||
const response = await axios.get(url.toString(), config);
|
||||
const response = await axios.get(url.toString(), await this.getConfig());
|
||||
const benchmark = validate(BenchmarkType, response.data);
|
||||
return benchmark;
|
||||
}
|
||||
|
@ -69,7 +104,7 @@ export class LaboratoryClient implements ILaboratory {
|
|||
throw new IllegalOperationError(message);
|
||||
}
|
||||
const url = new URL(`benchmarks/${name}`, this.endpoint);
|
||||
await axios.put(url.toString(), benchmark, config);
|
||||
await axios.put(url.toString(), benchmark, await this.getConfig());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -79,7 +114,7 @@ export class LaboratoryClient implements ILaboratory {
|
|||
/////////////////////////////////////////////////////////////////////////////
|
||||
async allCandidates(): Promise<ICandidate[]> {
|
||||
const url = new URL('candidates', this.endpoint);
|
||||
const response = await axios.get(url.toString(), config);
|
||||
const response = await axios.get(url.toString(), await this.getConfig());
|
||||
const candidates = validate(CandidateArrayType, response.data);
|
||||
return candidates;
|
||||
}
|
||||
|
@ -87,7 +122,7 @@ export class LaboratoryClient implements ILaboratory {
|
|||
async oneCandidate(rawName: string): Promise<ICandidate> {
|
||||
const name = normalizeName(rawName);
|
||||
const url = new URL(`candidates/${name}`, this.endpoint);
|
||||
const response = await axios.get(url.toString(), config);
|
||||
const response = await axios.get(url.toString(), await this.getConfig());
|
||||
const candidate = validate(CandidateType, response.data);
|
||||
return candidate;
|
||||
}
|
||||
|
@ -102,7 +137,7 @@ export class LaboratoryClient implements ILaboratory {
|
|||
throw new IllegalOperationError(message);
|
||||
}
|
||||
const url = new URL(`candidates/${name}`, this.endpoint);
|
||||
await axios.put(url.toString(), candidate, config);
|
||||
await axios.put(url.toString(), candidate, await this.getConfig());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -112,7 +147,7 @@ export class LaboratoryClient implements ILaboratory {
|
|||
/////////////////////////////////////////////////////////////////////////////
|
||||
async allSuites(): Promise<ISuite[]> {
|
||||
const url = new URL('suites', this.endpoint);
|
||||
const response = await axios.get(url.toString(), config);
|
||||
const response = await axios.get(url.toString(), await this.getConfig());
|
||||
const suites = validate(SuiteArrayType, response.data);
|
||||
return suites;
|
||||
}
|
||||
|
@ -120,7 +155,7 @@ export class LaboratoryClient implements ILaboratory {
|
|||
async oneSuite(rawName: string): Promise<ISuite> {
|
||||
const name = normalizeName(rawName);
|
||||
const url = new URL(`suites/${name}`, this.endpoint);
|
||||
const response = await axios.get(url.toString(), config);
|
||||
const response = await axios.get(url.toString(), await this.getConfig());
|
||||
const suite = validate(SuiteType, response.data);
|
||||
return suite;
|
||||
}
|
||||
|
@ -132,7 +167,7 @@ export class LaboratoryClient implements ILaboratory {
|
|||
throw new IllegalOperationError(message);
|
||||
}
|
||||
const url = new URL(`suites/${name}`, this.endpoint);
|
||||
await axios.put(url.toString(), suite, config);
|
||||
await axios.put(url.toString(), suite, await this.getConfig());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -142,7 +177,7 @@ export class LaboratoryClient implements ILaboratory {
|
|||
/////////////////////////////////////////////////////////////////////////////
|
||||
async allRuns(): Promise<IRun[]> {
|
||||
const url = new URL('runs', this.endpoint);
|
||||
const response = await axios.get(url.toString(), config);
|
||||
const response = await axios.get(url.toString(), await this.getConfig());
|
||||
const runs = validate(RunArrayType, response.data);
|
||||
return runs;
|
||||
}
|
||||
|
@ -150,14 +185,18 @@ export class LaboratoryClient implements ILaboratory {
|
|||
async oneRun(rawName: string): Promise<IRun> {
|
||||
const name = normalizeRunName(rawName);
|
||||
const url = new URL(`runs/${name}`, this.endpoint);
|
||||
const response = await axios.get(url.toString(), config);
|
||||
const response = await axios.get(url.toString(), await this.getConfig());
|
||||
const run = validate(RunType, response.data);
|
||||
return run;
|
||||
}
|
||||
|
||||
async createRunRequest(spec: IRunRequest): Promise<IRun> {
|
||||
const url = new URL('runs', this.endpoint);
|
||||
const response = await axios.post(url.toString(), spec, config);
|
||||
const response = await axios.post(
|
||||
url.toString(),
|
||||
spec,
|
||||
await this.getConfig()
|
||||
);
|
||||
const run = validate(RunType, response.data);
|
||||
return run;
|
||||
}
|
||||
|
@ -166,21 +205,21 @@ export class LaboratoryClient implements ILaboratory {
|
|||
const name = normalizeRunName(rawName);
|
||||
const url = new URL(`runs/${name}`, this.endpoint);
|
||||
const body: IUpdateRunStatus = { status };
|
||||
await axios.patch(url.toString(), body, config);
|
||||
await axios.patch(url.toString(), body, await this.getConfig());
|
||||
}
|
||||
|
||||
async reportRunResults(rawName: string, measures: Measures): Promise<void> {
|
||||
const name = normalizeRunName(rawName);
|
||||
const url = new URL(`runs/${name}/results`, this.endpoint);
|
||||
const body: IReportRunResults = { measures };
|
||||
await axios.post(url.toString(), body, config);
|
||||
await axios.post(url.toString(), body, await this.getConfig());
|
||||
}
|
||||
|
||||
async allRunResults(benchmark: string, suite: string): Promise<IResult[]> {
|
||||
const b = normalizeName(benchmark);
|
||||
const s = normalizeName(suite);
|
||||
const url = new URL(`runs?benchmark=${b}&suite=${s}`, this.endpoint);
|
||||
const response = await axios.get(url.toString(), config);
|
||||
const response = await axios.get(url.toString(), await this.getConfig());
|
||||
const results = validate(ResultArrayType, response.data);
|
||||
return results;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,26 @@ const createEnum = <E>(e: any, name: string): t.Type<E> => {
|
|||
return t.keyof(keys, name) as any;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IClientConnectionInfo
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
export const ClientConnectionInfoType = t.union([
|
||||
t.type({
|
||||
type: t.literal('aad'),
|
||||
clientId: t.string,
|
||||
authority: t.string,
|
||||
scopes: t.array(t.string),
|
||||
}),
|
||||
t.type({
|
||||
type: t.literal('unauthenticated'),
|
||||
}),
|
||||
]);
|
||||
export type IClientConnectionInfo = t.TypeOf<typeof ClientConnectionInfoType>;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// EntityBase
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import * as express from 'express';
|
||||
import * as errorhandler from 'strong-error-handler';
|
||||
import * as passport from 'passport';
|
||||
import { BearerStrategy } from 'passport-azure-ad';
|
||||
|
||||
import { IClientConnectionInfo } from '../../laboratory';
|
||||
|
||||
import { ILaboratory } from '../logic';
|
||||
|
||||
import { setErrorStatus } from './errors';
|
||||
|
||||
import {
|
||||
createBenchmarkRouter,
|
||||
createCandidateRouter,
|
||||
|
@ -12,11 +14,70 @@ import {
|
|||
createSuiteRouter,
|
||||
} from './routes';
|
||||
|
||||
export async function createApp(lab: ILaboratory): Promise<express.Express> {
|
||||
const app = express()
|
||||
.use(express.json())
|
||||
import {
|
||||
AuthConfiguration,
|
||||
AADConfiguration,
|
||||
AuthMode,
|
||||
NoAuthConfiguration,
|
||||
} from './configuration';
|
||||
|
||||
function configureAADAuth(app: express.Express, config: AADConfiguration) {
|
||||
passport.use(
|
||||
new BearerStrategy(
|
||||
{
|
||||
identityMetadata: `https://login.microsoftonline.com/${config.tenantId}/v2.0/.well-known/openid-configuration`,
|
||||
clientID: config.laboratoryClientId,
|
||||
},
|
||||
(token, done) => {
|
||||
return done(null, token);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// unauthenticated endpoint for clients to retrieve connection info
|
||||
app
|
||||
.get('/connect', (req, res) => {
|
||||
const connectionInfo: IClientConnectionInfo = {
|
||||
type: 'aad',
|
||||
clientId: config.cliClientId,
|
||||
authority: `https://login.microsoftonline.com/${config.tenantId}`,
|
||||
scopes: config.scopes,
|
||||
};
|
||||
res.json(connectionInfo);
|
||||
})
|
||||
|
||||
// require all endpoints to be authenticated
|
||||
.use(passport.initialize())
|
||||
.all('*', passport.authenticate('oauth-bearer', { session: false }));
|
||||
}
|
||||
|
||||
export async function createApp(
|
||||
lab: ILaboratory,
|
||||
auth: AuthConfiguration = NoAuthConfiguration
|
||||
): Promise<express.Express> {
|
||||
const app = express().use(express.json());
|
||||
|
||||
// configure authorization
|
||||
switch (auth.mode) {
|
||||
case AuthMode.AAD:
|
||||
configureAADAuth(app, auth as AADConfiguration);
|
||||
break;
|
||||
case AuthMode.None:
|
||||
default:
|
||||
app.get('/connect', (req, res) => {
|
||||
const connectionInfo: IClientConnectionInfo = {
|
||||
type: 'unauthenticated',
|
||||
};
|
||||
res.json(connectionInfo);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// Set up application routes
|
||||
app
|
||||
.get('/connect/validate', (req, res) => {
|
||||
res.status(200).end();
|
||||
})
|
||||
.use(createBenchmarkRouter(lab))
|
||||
.use(createCandidateRouter(lab))
|
||||
.use(createRunRouter(lab))
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import { QueueConfiguration } from '../../queue';
|
||||
import { DatabaseConfiguration } from '../../database';
|
||||
|
||||
export enum AuthMode {
|
||||
AAD = 'AAD',
|
||||
None = 'none',
|
||||
}
|
||||
|
||||
export interface AuthConfiguration {
|
||||
mode: AuthMode;
|
||||
}
|
||||
|
||||
export interface AADConfiguration extends AuthConfiguration {
|
||||
mode: AuthMode.AAD;
|
||||
tenantId: string;
|
||||
laboratoryClientId: string;
|
||||
cliClientId: string;
|
||||
scopes: string[];
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
export const NoAuthConfiguration = {
|
||||
mode: AuthMode.None,
|
||||
};
|
||||
|
||||
export interface LaboratoryConfiguration {
|
||||
endpointBaseUrl: string;
|
||||
port: number;
|
||||
queue: QueueConfiguration;
|
||||
database: DatabaseConfiguration;
|
||||
auth: AuthConfiguration;
|
||||
}
|
|
@ -16,7 +16,7 @@ async function main(argv: string[]) {
|
|||
|
||||
const lab = new SequelizeLaboratory(config.endpointBaseUrl, queue);
|
||||
|
||||
const app = await createApp(lab);
|
||||
const app = await createApp(lab, config.auth);
|
||||
app.listen(config.port, () => {
|
||||
console.log('Starting SDS laboratory service.');
|
||||
console.log(`Service url is ${config.endpointBaseUrl}.`);
|
||||
|
|
Загрузка…
Ссылка в новой задаче