diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index fadb4fb3cbc9..ace949b62e12 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -49,6 +49,7 @@ dependencies: '@rush-temp/perf-ai-form-recognizer': file:projects/perf-ai-form-recognizer.tgz '@rush-temp/perf-ai-metrics-advisor': file:projects/perf-ai-metrics-advisor.tgz '@rush-temp/perf-ai-text-analytics': file:projects/perf-ai-text-analytics.tgz + '@rush-temp/perf-core-rest-pipeline': file:projects/perf-core-rest-pipeline.tgz '@rush-temp/perf-eventgrid': file:projects/perf-eventgrid.tgz '@rush-temp/perf-identity': file:projects/perf-identity.tgz '@rush-temp/perf-keyvault-certificates': file:projects/perf-keyvault-certificates.tgz @@ -7843,7 +7844,7 @@ packages: dev: false name: '@rush-temp/ai-anomaly-detector' resolution: - integrity: sha512-BSoBzszi0XM9x7Dv2vIqf1SCCP0HIvWzcqJgk8bryS3stf5oCFIVzJ5qpzlb8MLXhg/OiLCA1dwhTUOD0pwM8A== + integrity: sha512-brftxxUrPMz0kTF4yzSTcKEulyQ+FG4co+yHqY3zvaxBTqZtLvreUYxkHEmTAEzuWuio1hQngZRPqhZ4zR6o7Q== tarball: file:projects/ai-anomaly-detector.tgz version: 0.0.0 file:projects/ai-document-translator.tgz: @@ -7929,7 +7930,7 @@ packages: dev: false name: '@rush-temp/ai-form-recognizer' resolution: - integrity: sha512-HUMGckigflkhjr74AUhw1mA05fWVbje5nS218AdXl0wrFsvMURVAAWba/igZx+9FJWQHDTrvnXOpljDPv1YRiQ== + integrity: sha512-i9w0AQc62Mt1TRd0IW5zLvoY1HBKywb6l/hFzniHBqXPa+yTBVXYuFguM/H50U0PBx6UUkj95bzGkONbBFKr/g== tarball: file:projects/ai-form-recognizer.tgz version: 0.0.0 file:projects/ai-metrics-advisor.tgz: @@ -7974,7 +7975,7 @@ packages: dev: false name: '@rush-temp/ai-metrics-advisor' resolution: - integrity: sha512-sbco9iDzQIZH/W1ha+BQfthAqikz7j3Ykt755Uv6Vc/uRitcQdTYCKWTspEpS/TJS04RFBrQbFxVgfJQQIUlaA== + integrity: sha512-tDR2qkusFWabAgO9vlaRyjFBs3DlEEkCFvBcImpxD0Uaf+xacnG2rbJIAAFGZcsp/YPShXR6T/AkUDouOiX10g== tarball: file:projects/ai-metrics-advisor.tgz version: 0.0.0 file:projects/ai-text-analytics.tgz: @@ -8022,7 +8023,7 @@ packages: dev: false name: '@rush-temp/ai-text-analytics' resolution: - integrity: sha512-lCqIX/6MmgfuTkCZ/iu21NuBgHPUGgrfNJBu8mJjtFQYVNegNOhd2MoBqVnUz7rp5p1fnI17lBsHVS2TbsrmQw== + integrity: sha512-a/yZ1UURt9He/DEYvd2KXDJrFKEDp07mEJXB3xl/wcEBPA5fieJvyA/DHUd5NNxyRjcnTA2o/5CdDQ/3SvRJcg== tarball: file:projects/ai-text-analytics.tgz version: 0.0.0 file:projects/app-configuration.tgz: @@ -8124,7 +8125,7 @@ packages: dev: false name: '@rush-temp/attestation' resolution: - integrity: sha512-308IFrOUbXclBJTqvF46pGIb+PckBwJ9PufzEE3dbB8mA9JIj+9838qEppUW8x9k2ENMx43+T6/FCh15qXsTgg== + integrity: sha512-mHFwGj+eb0v8zUD+YbgNfZ/Tf2fawnWNcvpN+PIqUsDPHFlouGXlTRWrbKXPoRRpMVn9xSIxNUkfsheZIgYc4Q== tarball: file:projects/attestation.tgz version: 0.0.0 file:projects/communication-chat.tgz: @@ -8287,7 +8288,7 @@ packages: dev: false name: '@rush-temp/communication-identity' resolution: - integrity: sha512-0QvoVTuWaVxaNqcm9kuUiJODW2qUB4uKPrHA8Pv3FGQQ7Y74P6Q9NaN2F9CP1TyXHQZlrD37p9LJnTQgbwXFqg== + integrity: sha512-s24VoSTH4Xb1UAkItnsfD+Y5LBBwm3N9NlTZxBKSB7UQyH6C6ZivQIzw3+Rw3KczJZClkebAKwVJ29gIVeipVg== tarball: file:projects/communication-identity.tgz version: 0.0.0 file:projects/communication-phone-numbers.tgz: @@ -8341,7 +8342,7 @@ packages: dev: false name: '@rush-temp/communication-phone-numbers' resolution: - integrity: sha512-ZxxqkvDAbF3E1MKUdq9dO3+d/Rzf81GValySfhgqqkq9GYmRIViBGzOJKPGs3cpM38w5ek5gd/bk8xzvEwWWAw== + integrity: sha512-+1SgvQ3laTzKg96UcCZDoS6s9bLPnZsLwP7EqkGF72EPaP5zQwKijUKk8+BNALSUR6nkZqdbfI6GxwwTKJfrtQ== tarball: file:projects/communication-phone-numbers.tgz version: 0.0.0 file:projects/communication-sms.tgz: @@ -8394,7 +8395,7 @@ packages: dev: false name: '@rush-temp/communication-sms' resolution: - integrity: sha512-iUDYbSKEqU9Onayru0k/Lzkp/tGDk5SuzxrcExxfXqhbCI0jIsL49TI9iEvPKvIZBwmauMnTTWG2dq6HG5c5dg== + integrity: sha512-SAyHi9GG9P15Gmwma1omjZqzqiNiprTahTe1ip9rvxVstxB7WF0FZMf7UyldGGLPbXr/aXmElcti41zz1wdgwA== tarball: file:projects/communication-sms.tgz version: 0.0.0 file:projects/container-registry.tgz: @@ -8441,7 +8442,7 @@ packages: dev: false name: '@rush-temp/container-registry' resolution: - integrity: sha512-JOd4JDWNPmckvkuO+IOmhWrO/9Rbp44M6Bm7VHySweQEavSxxMk+iAS7akisWRQfGhttzYaQ0jc4Py7rNmWF6A== + integrity: sha512-iYYsQn1lJgWNSOTrRBen2DvC13vCRumRHsieNimHrbvo9Cqi7AqDy0xxF55xyPREt8atUFtrTHqBWJI7zLbD8g== tarball: file:projects/container-registry.tgz version: 0.0.0 file:projects/core-amqp.tgz: @@ -9014,6 +9015,7 @@ packages: version: 0.0.0 file:projects/cosmos.tgz: dependencies: + '@azure/core-rest-pipeline': 1.0.3 '@azure/identity': 1.3.0_debug@4.3.1 '@microsoft/api-extractor': 7.7.11 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9062,7 +9064,7 @@ packages: dev: false name: '@rush-temp/cosmos' resolution: - integrity: sha512-zr5rSxxyf9pXAedweom+O1DR8GDklyAWW4QOyHiqd18Ja4FuQ6dPJiRejNVOyg6SE4xSl6cbf8agjPfG3upEnQ== + integrity: sha512-wCMZgt5C551rCi6HXgvD/EZVo6sl3llky3jsRCVQBcamdjmNF0qphRBDyYLlAn1cysnA7eG4clCBGzqT5wsAIg== tarball: file:projects/cosmos.tgz version: 0.0.0 file:projects/data-tables.tgz: @@ -9213,7 +9215,7 @@ packages: dev: false name: '@rush-temp/digital-twins-core' resolution: - integrity: sha512-HATRz9UgjGJhJhHWrb4PL9PRfnCe/kjkDIyJZUGuA2ZFn8HIkDlR8y53sXJU32OZKTlbvCPjkq+odAQUmH5m9A== + integrity: sha512-V0UtuepoVa9p4fiTuqKNHi5UOjfa0jVoDucviYPRBPX4d3dJK0JUA+oPTJSbS6dT0YPxB8P5ojh8AzipG0/dcg== tarball: file:projects/digital-twins-core.tgz version: 0.0.0 file:projects/eslint-plugin-azure-sdk.tgz: @@ -9550,7 +9552,7 @@ packages: optionalDependencies: keytar: 7.6.0 resolution: - integrity: sha512-TGmDn9FURoFYW60Z94hZ8+QRix4SX+A0jZGBJUZ4Xvo9IutZ81CVMxctI+TL8SgyWLzLWHkSEjsbB36WcVQM9w== + integrity: sha512-sEvBei3gVktijypQM6FLi74OeRLYeUlJMgmHXoAyYOaJDE+SbowgsRYQeEwhvloeNACGBX6ERgp6GmeEG/UEcg== tarball: file:projects/identity.tgz version: 0.0.0 file:projects/iot-device-update.tgz: @@ -9623,7 +9625,7 @@ packages: dev: false name: '@rush-temp/keyvault-admin' resolution: - integrity: sha512-oRwQPIUfeV8PrFW1HB06AWis2jNtWXz99fenuzpH0C+iVCmqDTU0zGFUBmVHW5qqjykEtoHCyyXp6L8WgIpZGw== + integrity: sha512-TjTulyn5sh+azdHJh6GDWR9k7lgzWR8tj+ei5Kq9POeucNXlJ/fVbTmp2Z20wNTXAHOjGxq17G2j/nOnTYnmEA== tarball: file:projects/keyvault-admin.tgz version: 0.0.0 file:projects/keyvault-certificates.tgz: @@ -9681,7 +9683,7 @@ packages: dev: false name: '@rush-temp/keyvault-certificates' resolution: - integrity: sha512-geYalPzb+uX/KwZtzL95nr+8oUXOBVk85Z1435k32tkxHLyRFTmALCRWqUQ8667TnyWWh+9ckR62/aa7OIcFAQ== + integrity: sha512-CaA2BasCWbxEUhdf/mJ6V1HZkTXhigYkNmJiPN0h2W0I/lPKdzIiHBRZdedesrwnyHofkcczkf6EQmMgVacdGQ== tarball: file:projects/keyvault-certificates.tgz version: 0.0.0 file:projects/keyvault-common.tgz: @@ -9755,7 +9757,7 @@ packages: dev: false name: '@rush-temp/keyvault-keys' resolution: - integrity: sha512-d5VpsD0EqzARzk9c9a6E+1sgIjAZYCObYjQCIbEZNCem4IB5CIYlm83c+/DRBKyH1fQEVsoHQgxTwbNyRkrxNA== + integrity: sha512-YpRyojpWpxqV8joiplBq2zm0ISpEbJc7NUHg3TKrtZkPr0HyuA1G7mFH8lIqtZga3x90zh61Vsa2wsFYhyng0g== tarball: file:projects/keyvault-keys.tgz version: 0.0.0 file:projects/keyvault-secrets.tgz: @@ -9813,7 +9815,7 @@ packages: dev: false name: '@rush-temp/keyvault-secrets' resolution: - integrity: sha512-s0dLzF1S5wyX4UbfxHMkp3HH7E6vw3WyyBhvWiS59msK0yxQj2f/gsv+ZZ6+GWU15ALexiaSIQE7RrNRrVvAfg== + integrity: sha512-z0BjfNxui81pHbY/0O0wsJBDkRsGmESce296UZ2sEHiUc/mZkchky66bHslL2CZ3Fk5tcWsPgcGg52gDxHqN6A== tarball: file:projects/keyvault-secrets.tgz version: 0.0.0 file:projects/logger.tgz: @@ -10004,6 +10006,22 @@ packages: integrity: sha512-WRKlYq5E3/zAp5qMYA5yQagbJYlfuIUsxCBp+WSfENbW/m4WzpjkNC/01TLJ6tO2CEoLc+RRPXCDFpA30H432g== tarball: file:projects/perf-ai-text-analytics.tgz version: 0.0.0 + file:projects/perf-core-rest-pipeline.tgz: + dependencies: + '@types/uuid': 8.3.0 + dotenv: 8.2.0 + eslint: 7.23.0 + prettier: 1.19.1 + rimraf: 3.0.2 + ts-node: 9.1.1_typescript@4.2.4 + tslib: 2.2.0 + typescript: 4.2.4 + dev: false + name: '@rush-temp/perf-core-rest-pipeline' + resolution: + integrity: sha512-ir4B6tj+vRwKlprAUeiJSj7JCAzhq0K4MM5Uj6HA7Qzm+xHSY58jAbQXCKqGZu7mt4uOlb1sYrMEm0dIUBQHaQ== + tarball: file:projects/perf-core-rest-pipeline.tgz + version: 0.0.0 file:projects/perf-eventgrid.tgz: dependencies: '@types/node': 8.10.66 @@ -10215,7 +10233,7 @@ packages: dev: false name: '@rush-temp/quantum-jobs' resolution: - integrity: sha512-ZGwwkEsI5mFwQKjnxCWlrSWdanAQmBfWlH2GOQBaDY0Dr7+ttS6/UcgAEAUqRy5CKpwtzEOw7hCWw0328wgKvA== + integrity: sha512-kqELcctIfSAZsgkMARVhBAzqyQFpJP/BImOS6XEARXRMe8kXlNDfJAn9u569nMQgegeAxZW/wMbQxbUsEHzgZw== tarball: file:projects/quantum-jobs.tgz version: 0.0.0 file:projects/schema-registry-avro.tgz: @@ -10271,7 +10289,7 @@ packages: dev: false name: '@rush-temp/schema-registry-avro' resolution: - integrity: sha512-IkbCxJeQAMMEP5TNnCNwwoJpCwuxiVRh7mwTBSQF7wcsT+qQsThEG1sdGZJDN4USU47H+bOLTIn4i6HzpfAvaA== + integrity: sha512-uTOsbCwfctFbxbRgGGKoz9NAuCIyMehznoMdz8noI5tZ7nUVRpnmSzh+sY1AA4/2/dKgMhTlgcVtFjGGgNU2+Q== tarball: file:projects/schema-registry-avro.tgz version: 0.0.0 file:projects/schema-registry.tgz: @@ -10323,7 +10341,7 @@ packages: dev: false name: '@rush-temp/schema-registry' resolution: - integrity: sha512-zfAsAov4Ijx3nUSubjGXdLh6gEH6KE29iuZTSVqfzfVdmofxIXJBde0qRGfhr+tExpuvOygoBKeCLrDYtNI6HQ== + integrity: sha512-PfosQpd4CbfdyFXBKAJsZFUJ9P+YwX131MiYGCUQwdWamWfKg4fSGo7+fPAOENODNf9rPe4gahmmonDYuke+IQ== tarball: file:projects/schema-registry.tgz version: 0.0.0 file:projects/search-documents.tgz: @@ -10571,7 +10589,7 @@ packages: dev: false name: '@rush-temp/storage-blob' resolution: - integrity: sha512-CR55ZzmPi/l7XwTZdOY2mvJU2OF6OL6aloG8wLv+pLaljwm2uf600JhMvgD5PzCh3d4/jtctZtsNq8WDUwgrng== + integrity: sha512-tPl6kzd8U3mX7Js6CS2keRFcS2SkC2FcAGff0LoZ8AkBxK6Rp5vHvG8Aqsuf6pmnPSXGHCSX1a5HWD7G8BEPuw== tarball: file:projects/storage-blob.tgz version: 0.0.0 file:projects/storage-file-datalake.tgz: @@ -10631,7 +10649,7 @@ packages: dev: false name: '@rush-temp/storage-file-datalake' resolution: - integrity: sha512-ru/ZA4Wf0gfKw5rRPBaavkwJYFftxm6j+S7uBJ7LgIpZplMidq2munB47jFBgjf0uaxHJ1Q/y/ssDCTrxFafTg== + integrity: sha512-0NClo8xWcTdIobuN51EuCMnHN/M2fHzsas3lCP9H3dZ+UXRWRqBGjGPZzcuJtgqiu+kp7Be0xlRklw5/OSyReQ== tarball: file:projects/storage-file-datalake.tgz version: 0.0.0 file:projects/storage-file-share.tgz: @@ -10863,7 +10881,7 @@ packages: dev: false name: '@rush-temp/synapse-artifacts' resolution: - integrity: sha512-Y9sYEN8tQuoziYkBrOTK+gAe4iXdFYr4/HCMPKLgqm1Q2qyh3KCpBjtse060cilZpi8mazJiQuRsN5dipDx2+Q== + integrity: sha512-5P3ENKx4RYknSooxYT4OzAYg4unjEdcTgwrKXYrkXMp0y73ps3KpPJtdtOSFLESuEX/j1CNv8T7kwz2OIl4nUw== tarball: file:projects/synapse-artifacts.tgz version: 0.0.0 file:projects/synapse-managed-private-endpoints.tgz: @@ -10969,7 +10987,7 @@ packages: dev: false name: '@rush-temp/template' resolution: - integrity: sha512-mwHFZ1xypf2mBlIcqY/yi+3JKpTC54l/ZdImtdQ0ioOPW8ia9HXOVdK5Gc0Kmk0Bhe4Wjw+K1cnBPSbB5WlFXA== + integrity: sha512-GEg0iurjiFVFHToKkPuprmMO5F5ivG6+imfPGqPYlP5HDnyOFnTdEiILEcPV/bnNpM7ItrWDQToZFFTu8d1UAQ== tarball: file:projects/template.tgz version: 0.0.0 file:projects/test-utils-multi-version.tgz: @@ -11138,7 +11156,7 @@ packages: dev: false name: '@rush-temp/web-pubsub-express' resolution: - integrity: sha512-lLMpNd4OKyuyJbikDKMYDDNlzEjtK/NSZh0QbUNfEIfEWs3HyUcfxzN8wKr9fKeE345rtK5DCl/2ztU1PLYj9A== + integrity: sha512-X1/olijTQ0DqPM4c8OBL6JzZ3Nc86uzmqxrUSBxpysxc7cHGkVN22rVls+SUPdoqsMDSBYZO1RnCn3W1/iIDUQ== tarball: file:projects/web-pubsub-express.tgz version: 0.0.0 file:projects/web-pubsub.tgz: @@ -11252,6 +11270,7 @@ specifiers: '@rush-temp/perf-ai-form-recognizer': file:./projects/perf-ai-form-recognizer.tgz '@rush-temp/perf-ai-metrics-advisor': file:./projects/perf-ai-metrics-advisor.tgz '@rush-temp/perf-ai-text-analytics': file:./projects/perf-ai-text-analytics.tgz + '@rush-temp/perf-core-rest-pipeline': file:./projects/perf-core-rest-pipeline.tgz '@rush-temp/perf-eventgrid': file:./projects/perf-eventgrid.tgz '@rush-temp/perf-identity': file:./projects/perf-identity.tgz '@rush-temp/perf-keyvault-certificates': file:./projects/perf-keyvault-certificates.tgz diff --git a/rush.json b/rush.json index 320fb6e83145..4192901cc35d 100644 --- a/rush.json +++ b/rush.json @@ -726,6 +726,11 @@ "packageName": "@azure-tests/perf-ai-metrics-advisor", "projectFolder": "sdk/metricsadvisor/perf-tests/ai-metrics-advisor", "versionPolicyName": "test" + }, + { + "packageName": "@azure-tests/perf-core-rest-pipeline", + "projectFolder": "sdk/core/perf-tests/core-rest-pipeline", + "versionPolicyName": "test" } ] } diff --git a/sdk/core/perf-tests/core-rest-pipeline/CHANGELOG.md b/sdk/core/perf-tests/core-rest-pipeline/CHANGELOG.md new file mode 100644 index 000000000000..0c9fe9cda1d4 --- /dev/null +++ b/sdk/core/perf-tests/core-rest-pipeline/CHANGELOG.md @@ -0,0 +1,5 @@ +# Release History + +## 1.0.0-beta.1 (Unreleased) + +- Added a first performance test that processes WWW-Authenticate challenges. diff --git a/sdk/core/perf-tests/core-rest-pipeline/README.md b/sdk/core/perf-tests/core-rest-pipeline/README.md new file mode 100644 index 000000000000..f3abe9c57d51 --- /dev/null +++ b/sdk/core/perf-tests/core-rest-pipeline/README.md @@ -0,0 +1,9 @@ +### Guide + +1. Build the core-rest-pipeline perf tests package `rush build -t perf-core-rest-pipeline`. +3. Copy the `sample.env` file and name it as `.env`. +4. Populate the `.env` file with your Azure Credentials. +5. Refer to the [rate limits](https://docs.microsoft.com/azure/active-directory/enterprise-users/directory-service-limits-restrictions) and then run the tests as follows: + +- `bearerTokenChallengeAuthenticationPolicy` test for the `challengeCallbacks`, for simple `WWW-Authenticate` challenges. + - `npm run perf-test:node -- BearerTokenChallengeAuthenticationPolicyTest --warmup 1 --iterations 1 --parallel 5` diff --git a/sdk/core/perf-tests/core-rest-pipeline/package.json b/sdk/core/perf-tests/core-rest-pipeline/package.json new file mode 100644 index 000000000000..d04fb4063e32 --- /dev/null +++ b/sdk/core/perf-tests/core-rest-pipeline/package.json @@ -0,0 +1,48 @@ +{ + "name": "@azure-tests/perf-core-rest-pipeline", + "version": "1.0.0-beta.1", + "description": "", + "main": "", + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@azure/core-rest-pipeline": "1.1.0-beta.1", + "@azure/core-auth": "^1.3.0", + "@azure/test-utils-perfstress": "^1.0.0", + "dotenv": "^8.2.0" + }, + "devDependencies": { + "@types/uuid": "^8.0.0", + "eslint": "^7.15.0", + "prettier": "^1.16.4", + "rimraf": "^3.0.0", + "ts-node": "^9.0.0", + "tslib": "^2.0.0", + "typescript": "~4.2.0" + }, + "private": true, + "scripts": { + "perf-test:node": "ts-node test/index.spec.ts", + "audit": "node ../../../common/scripts/rush-audit.js && rimraf node_modules package-lock.json && npm i --package-lock-only 2>&1 && npm audit", + "build": "tsc -p .", + "build:samples": "echo skipped", + "build:test": "echo skipped", + "check-format": "prettier --list-different --config ../../../../.prettierrc.json --ignore-path ../../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"", + "clean": "rimraf dist dist-esm test-dist typings *.tgz *.log", + "format": "prettier --write --config ../../../../.prettierrc.json --ignore-path ../../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"", + "integration-test:browser": "echo skipped", + "integration-test:node": "echo skipped", + "integration-test": "echo skipped", + "lint:fix": "eslint --no-eslintrc -c ../../../.eslintrc.internal.json package.json test --ext .ts --fix --fix-type [problem,suggestion]", + "lint": "eslint --no-eslintrc -c ../../../.eslintrc.internal.json package.json test --ext .ts", + "pack": "npm pack 2>&1", + "prebuild": "npm run clean", + "unit-test:browser": "echo skipped", + "unit-test:node": "echo skipped", + "unit-test": "echo skipped", + "test:browser": "echo skipped", + "test:node": "echo skipped", + "test": "echo skipped" + } +} diff --git a/sdk/core/perf-tests/core-rest-pipeline/sample.env b/sdk/core/perf-tests/core-rest-pipeline/sample.env new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sdk/core/perf-tests/core-rest-pipeline/test/bearerTokenChallengeAuthenticationPolicy/wwwChallenge.spec.ts b/sdk/core/perf-tests/core-rest-pipeline/test/bearerTokenChallengeAuthenticationPolicy/wwwChallenge.spec.ts new file mode 100644 index 000000000000..2d5fc6245618 --- /dev/null +++ b/sdk/core/perf-tests/core-rest-pipeline/test/bearerTokenChallengeAuthenticationPolicy/wwwChallenge.spec.ts @@ -0,0 +1,200 @@ +import { PerfStressTest } from "@azure/test-utils-perfstress"; +import { AccessToken, GetTokenOptions, TokenCredential } from "@azure/core-auth"; +import { + AuthorizeRequestOnChallengeOptions, + bearerTokenChallengeAuthenticationPolicy, + createEmptyPipeline, + createHttpHeaders, + createPipelineRequest, + HttpClient, + Pipeline, + PipelineRequest, + PipelineResponse +} from "@azure/core-rest-pipeline"; +import { TextDecoder } from "util"; + +export interface TestChallenge { + scope: string; + claims: string; +} + +let cachedChallenge: string | undefined; + +/** + * Converts a uint8Array to a string. + */ +export function uint8ArrayToString(ab: Uint8Array): string { + const decoder = new TextDecoder("utf-8"); + return decoder.decode(ab); +} + +/** + * Encodes a string in base64 format. + * @param value - The string to encode + */ +export function encodeString(value: string): string { + return Buffer.from(value).toString("base64"); +} + +/** + * Decodes a base64 string into a byte array. + * @param value - The base64 string to decode + */ +export function decodeString(value: string): Uint8Array { + return Buffer.from(value, "base64"); +} + +// Converts: +// Bearer a="b", c="d", Bearer d="e", f="g" +// Into: +// [ { a: 'b', c: 'd' }, { d: 'e', f: 'g"' } ] +// Important: +// Do not use this in production, as values might contain the strings we use to split things up. +function parseCAEChallenge(challenges: string): any[] { + return challenges + .split("Bearer ") + .filter((x) => x) + .map((challenge) => + `${challenge.trim()}, ` + .split('", ') + .filter((x) => x) + .map((keyValue) => (([key, value]) => ({ [key]: value }))(keyValue.trim().split('="'))) + .reduce((a, b) => ({ ...a, ...b }), {}) + ); +} + +async function authorizeRequestOnChallenge( + options: AuthorizeRequestOnChallengeOptions +): Promise { + const { scopes, request, response, getAccessToken } = options; + + const challenge = response?.headers.get("WWW-Authenticate"); + if (!challenge) { + throw new Error("Failed to retrieve challenge from response headers"); + } + + const challenges: TestChallenge[] = parseCAEChallenge(challenge) || []; + + const parsedChallenge = challenges.find((x) => x.claims); + if (!parsedChallenge) { + throw new Error("Missing claims"); + } + if (cachedChallenge !== challenge) { + cachedChallenge = challenge; + } + + const accessToken = await getAccessToken(scopes, { + // The architects haven't decided: + // claims: uint8ArrayToString(Buffer.from(parsedChallenge.claims, "base64")) + }); + + if (!accessToken) { + return false; + } + + request.headers.set("Authorization", `Bearer ${accessToken}`); + return true; +} + +class MockRefreshAzureCredential implements TokenCredential { + public authCount = 0; + public scopesAndClaims: { scope: string | string[]; challengeClaims: string | undefined }[] = []; + + constructor(public getTokenResponse: AccessToken) {} + + public getToken( + scope: string | string[], + _options: GetTokenOptions + ): Promise { + this.authCount++; + this.scopesAndClaims.push({ + scope, + // Architects haven't decided about the claims property + challengeClaims: undefined // options.claims + }); + return Promise.resolve(this.getTokenResponse); + } +} + +export class BearerTokenChallengeAuthenticationPolicyTest extends PerfStressTest { + options = {}; + + constructor() { + super(); + } + + static pipeline?: Pipeline; + static testHttpsClient?: HttpClient; + static request?: PipelineRequest; + + async globalSetup(): Promise { + const scope = "http://localhost/.default"; + const challengeClaims = JSON.stringify({ + access_token: { foo: "bar" } + }); + + const request = createPipelineRequest({ url: "https://example.com" }); + const responses: PipelineResponse[] = [ + { + headers: createHttpHeaders({ + "WWW-Authenticate": `Bearer scope="${scope}", claims="${encodeString(challengeClaims)}"` + }), + request, + status: 401 + }, + { + headers: createHttpHeaders(), + request, + status: 200 + } + ]; + + const expiresOn = Date.now() + 5000; + const getTokenResponse = { token: "mock-token", expiresOnTimestamp: expiresOn }; + const credential = new MockRefreshAzureCredential(getTokenResponse); + + const pipeline = createEmptyPipeline(); + + const cachedToken: AccessToken | null = null; + const bearerPolicy = bearerTokenChallengeAuthenticationPolicy({ + // Intentionally left empty, as it should be replaced by the challenge. + scopes: [""], + credential, + challengeCallbacks: { + async authorizeRequest({ request }) { + if (cachedToken) { + request.headers.set("Authorization", `Bearer ${cachedToken}`); + } + }, + authorizeRequestOnChallenge + } + }); + + pipeline.addPolicy(bearerPolicy); + + const finalSendRequestHeaders: (string | undefined)[] = []; + + let responsesCount = 0; + + BearerTokenChallengeAuthenticationPolicyTest.request = request; + BearerTokenChallengeAuthenticationPolicyTest.pipeline = pipeline; + BearerTokenChallengeAuthenticationPolicyTest.testHttpsClient = { + sendRequest: async (req) => { + finalSendRequestHeaders.push(req.headers.get("Authorization")); + + // First goes the error response, then the good response. + const responsesIndex = responsesCount % 2 ? 1 : 0; + + const response = responses[responsesIndex]; + responsesCount++; + response.request = req; + return response; + } + }; + } + + async runAsync(): Promise { + const { pipeline, testHttpsClient, request } = BearerTokenChallengeAuthenticationPolicyTest; + await pipeline!.sendRequest(testHttpsClient!, request!); + } +} diff --git a/sdk/core/perf-tests/core-rest-pipeline/test/index.spec.ts b/sdk/core/perf-tests/core-rest-pipeline/test/index.spec.ts new file mode 100644 index 000000000000..ff95ff93f2d7 --- /dev/null +++ b/sdk/core/perf-tests/core-rest-pipeline/test/index.spec.ts @@ -0,0 +1,10 @@ +import { PerfStressProgram, selectPerfStressTest } from "@azure/test-utils-perfstress"; +import { BearerTokenChallengeAuthenticationPolicyTest } from "./bearerTokenChallengeAuthenticationPolicy/wwwChallenge.spec"; + +console.log("=== Starting the perfStress test ==="); + +const perfStressProgram = new PerfStressProgram( + selectPerfStressTest([BearerTokenChallengeAuthenticationPolicyTest]) +); + +perfStressProgram.run(); diff --git a/sdk/core/perf-tests/core-rest-pipeline/tsconfig.json b/sdk/core/perf-tests/core-rest-pipeline/tsconfig.json new file mode 100644 index 000000000000..afd0da5d14c7 --- /dev/null +++ b/sdk/core/perf-tests/core-rest-pipeline/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../../../tsconfig.package", + "compilerOptions": { + "module": "CommonJS", + "target": "ES2015", + "lib": ["ES6", "ESNext.AsyncIterable"], + "noEmit": true + }, + "compileOnSave": true, + "exclude": ["node_modules"], + "include": ["./test/**/*.ts"] +}