diff --git a/docker_images/herald_executor/worker.py b/docker_images/herald_executor/worker.py index b94451b3..f94f378a 100644 --- a/docker_images/herald_executor/worker.py +++ b/docker_images/herald_executor/worker.py @@ -246,12 +246,11 @@ def format_remained_time(start_date, end_date): for booking in shareable_booking_data: acquired_since = booking['acquired_since'] released_at = booking['released_at'] - acquired_by_id = booking.get('acquired_by', {}).get('id') utc_acquired_since = int( utcfromtimestamp(acquired_since).timestamp()) utc_released_at = int( utcfromtimestamp(released_at).timestamp()) - user_name = employee_id_map.get(acquired_by_id, {}).get('name') + user_name = booking.get('acquired_by', {}).get('name') if not user_name: LOG.error('Could not detect employee name for booking %s', booking['id']) diff --git a/jira_ui/server/package-lock.json b/jira_ui/server/package-lock.json index c893df20..9f201b04 100644 --- a/jira_ui/server/package-lock.json +++ b/jira_ui/server/package-lock.json @@ -11,7 +11,7 @@ "license": "ISC", "dependencies": { "dotenv": "^16.3.1", - "express": "^4.21.1", + "express": "^4.21.2", "express-rate-limit": "^6.10.0" }, "devDependencies": { @@ -303,9 +303,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -326,7 +326,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -341,6 +341,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express-rate-limit": { @@ -815,9 +819,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -1347,9 +1351,9 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -1370,7 +1374,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -1706,9 +1710,9 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "picomatch": { "version": "2.3.1", diff --git a/jira_ui/server/package.json b/jira_ui/server/package.json index 9eabe5d9..2895c3d1 100644 --- a/jira_ui/server/package.json +++ b/jira_ui/server/package.json @@ -14,7 +14,7 @@ "license": "ISC", "dependencies": { "dotenv": "^16.3.1", - "express": "^4.21.1", + "express": "^4.21.2", "express-rate-limit": "^6.10.0" }, "devDependencies": { diff --git a/jira_ui/ui/package-lock.json b/jira_ui/ui/package-lock.json index 08da5992..1a233eb3 100644 --- a/jira_ui/ui/package-lock.json +++ b/jira_ui/ui/package-lock.json @@ -20,7 +20,7 @@ "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@vitejs/plugin-react-swc": "^3.7.0", "dotenv": "^16.3.1", - "express": "^4.21.1", + "express": "^4.21.2", "http-proxy-middleware": "^2.0.7", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -5829,9 +5829,9 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -5852,7 +5852,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -5867,6 +5867,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/debug": { @@ -8273,9 +8277,9 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "node_modules/path-type": { "version": "4.0.0", @@ -14076,9 +14080,9 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -14099,7 +14103,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -15749,9 +15753,9 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "path-type": { "version": "4.0.0", diff --git a/jira_ui/ui/package.json b/jira_ui/ui/package.json index 544fcaca..33a6b201 100644 --- a/jira_ui/ui/package.json +++ b/jira_ui/ui/package.json @@ -16,7 +16,7 @@ "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@vitejs/plugin-react-swc": "^3.7.0", "dotenv": "^16.3.1", - "express": "^4.21.1", + "express": "^4.21.2", "http-proxy-middleware": "^2.0.7", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/ngui/server/package.json b/ngui/server/package.json index 27ec0693..704bc409 100644 --- a/ngui/server/package.json +++ b/ngui/server/package.json @@ -31,7 +31,7 @@ "@types/node": "^20.14.9", "body-parser": "^1.20.3", "cors": "^2.8.5", - "express": "^4.21.1", + "express": "^4.21.2", "graphql": "^16.9.0", "graphql-scalars": "^1.23.0", "http-proxy-middleware": "^2.0.7", diff --git a/ngui/server/pnpm-lock.yaml b/ngui/server/pnpm-lock.yaml index d7e1cf29..b57e51a6 100644 --- a/ngui/server/pnpm-lock.yaml +++ b/ngui/server/pnpm-lock.yaml @@ -34,8 +34,8 @@ dependencies: specifier: ^2.8.5 version: 2.8.5 express: - specifier: ^4.21.1 - version: 4.21.1 + specifier: ^4.21.2 + version: 4.21.2 graphql: specifier: ^16.9.0 version: 16.9.0 @@ -151,7 +151,7 @@ packages: '@types/node-fetch': 2.6.11 async-retry: 1.3.3 cors: 2.8.5 - express: 4.21.1 + express: 4.21.2 graphql: 16.9.0 loglevel: 1.9.2 lru-cache: 7.18.3 @@ -2472,8 +2472,8 @@ packages: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} dev: false - /express@4.21.1: - resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==} + /express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} dependencies: accepts: 1.3.8 @@ -2495,7 +2495,7 @@ packages: methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 - path-to-regexp: 0.1.10 + path-to-regexp: 0.1.12 proxy-addr: 2.0.7 qs: 6.13.0 range-parser: 1.2.1 @@ -3498,8 +3498,8 @@ packages: path-root-regex: 0.1.2 dev: true - /path-to-regexp@0.1.10: - resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} + /path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} dev: false /path-type@4.0.0: diff --git a/ngui/ui/package.json b/ngui/ui/package.json index da13aa63..72718458 100644 --- a/ngui/ui/package.json +++ b/ngui/ui/package.json @@ -36,7 +36,7 @@ "d3-array": "^3.2.4", "d3-scale": "^4.0.2", "date-fns": "^2.29.3", - "express": "^4.21.1", + "express": "^4.21.2", "file-saver": "^2.0.5", "github-buttons": "^2.27.0", "google-map-react": "^2.2.1", diff --git a/ngui/ui/pnpm-lock.yaml b/ngui/ui/pnpm-lock.yaml index 38331523..b471b76a 100644 --- a/ngui/ui/pnpm-lock.yaml +++ b/ngui/ui/pnpm-lock.yaml @@ -105,8 +105,8 @@ dependencies: specifier: ^2.29.3 version: 2.29.3 express: - specifier: ^4.21.1 - version: 4.21.1 + specifier: ^4.21.2 + version: 4.21.2 file-saver: specifier: ^2.0.5 version: 2.0.5 @@ -4640,7 +4640,7 @@ packages: ejs: 3.1.10 esbuild: 0.18.20 esbuild-plugin-alias: 0.2.1 - express: 4.21.1 + express: 4.21.2 find-cache-dir: 3.3.2 fs-extra: 11.2.0 process: 0.11.10 @@ -4676,7 +4676,7 @@ packages: '@types/find-cache-dir': 3.2.1 browser-assert: 1.2.1 es-module-lexer: 0.9.3 - express: 4.21.1 + express: 4.21.2 find-cache-dir: 3.3.2 fs-extra: 11.2.0 magic-string: 0.30.5 @@ -4735,7 +4735,7 @@ packages: detect-indent: 6.1.0 envinfo: 7.13.0 execa: 5.1.1 - express: 4.21.1 + express: 4.21.2 find-up: 5.0.0 fs-extra: 11.2.0 get-npm-tarball-url: 2.1.0 @@ -4900,7 +4900,7 @@ packages: cli-table3: 0.6.5 compression: 1.7.4 detect-port: 1.6.1 - express: 4.21.1 + express: 4.21.2 fs-extra: 11.2.0 globby: 11.1.0 lodash: 4.17.21 @@ -8619,8 +8619,8 @@ packages: jest-util: 29.7.0 dev: true - /express@4.21.1: - resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==} + /express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} dependencies: accepts: 1.3.8 @@ -8642,7 +8642,7 @@ packages: methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 - path-to-regexp: 0.1.10 + path-to-regexp: 0.1.12 proxy-addr: 2.0.7 qs: 6.13.0 range-parser: 1.2.1 @@ -11932,8 +11932,8 @@ packages: minipass: 7.0.4 dev: true - /path-to-regexp@0.1.10: - resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} + /path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} diff --git a/optscale_client/rest_api_client/client_v2.py b/optscale_client/rest_api_client/client_v2.py index dd99910b..f1cfb412 100644 --- a/optscale_client/rest_api_client/client_v2.py +++ b/optscale_client/rest_api_client/client_v2.py @@ -2106,8 +2106,10 @@ def layouts_list(self, org_id, layout_type=None, include_shared=False, entity_id=entity_id, token=token) return self.get(url) - def layout_get(self, org_id, layout_id): + def layout_get(self, org_id, layout_id, token=None): url = self.layouts_url(org_id, layout_id) + if token: + url += self.query_url(token=token) return self.get(url) def layout_update(self, org_id, layout_id, params): @@ -2197,8 +2199,11 @@ def artifact_update(self, org_id, artifact_id, **params): def artifacts_get(self, org_id, **params): return self.get(self.artifacts_url(org_id) + self.query_url(**params)) - def artifact_get(self, org_id, artifact_id): - return self.get(self.artifacts_url(org_id, artifact_id)) + def artifact_get(self, org_id, artifact_id, token=None): + url = self.artifacts_url(org_id, artifact_id) + if token: + url += self.query_url(token=token) + return self.get(url) def artifact_delete(self, org_id, artifact_id): return self.delete(self.artifacts_url(org_id, artifact_id)) diff --git a/rest_api/rest_api_server/handlers/v2/layouts.py b/rest_api/rest_api_server/handlers/v2/layouts.py index 0db5face..d7751e5f 100644 --- a/rest_api/rest_api_server/handlers/v2/layouts.py +++ b/rest_api/rest_api_server/handlers/v2/layouts.py @@ -1,13 +1,12 @@ import json from rest_api.rest_api_server.controllers.layout import LayoutsAsyncController -from rest_api.rest_api_server.handlers.v2.base import BaseHandler from rest_api.rest_api_server.handlers.v2.profiling.base import ( ProfilingHandler) from rest_api.rest_api_server.handlers.v1.base_async import ( BaseAsyncItemHandler, BaseAsyncCollectionHandler) from rest_api.rest_api_server.handlers.v1.base import ( - BaseAuthHandler, BaseAuthQueryTokenHandler) + BaseAuthQueryTokenHandler) from rest_api.rest_api_server.utils import (run_task, ModelEncoder) @@ -222,7 +221,7 @@ async def post(self, organization_id, **_url_params): class LayoutsAsyncItemHandler( - BaseAsyncItemHandler, BaseAuthHandler, BaseHandler): + BaseAsyncItemHandler, BaseAuthQueryTokenHandler, ProfilingHandler): def _get_controller_class(self): return LayoutsAsyncController @@ -245,6 +244,12 @@ async def get(self, organization_id, layout_id): description: Layout id required: true type: string + - name: token + in: query + description: | + Unique token related to organization profiling token + required: false + type: string responses: 200: description: Layout @@ -281,11 +286,19 @@ async def get(self, organization_id, layout_id): security: - token: [] """ - await self.check_permissions( + secret = False + token = self.get_arg('token', str, None) + if await self.check_md5_profiling_token( + organization_id, token, raises=False): + user_id = None + secret = True + else: + await self.check_permissions( 'INFO_ORGANIZATION', 'organization', organization_id) - user_id = await self.check_self_auth() + user_id = await self.check_self_auth() res = await run_task( - self.controller.get_item, user_id, organization_id, layout_id) + self.controller.get_item, user_id, organization_id, layout_id, + secret=secret) self.write(json.dumps(res.to_dict())) async def patch(self, organization_id, layout_id): diff --git a/rest_api/rest_api_server/handlers/v2/profiling/artifacts.py b/rest_api/rest_api_server/handlers/v2/profiling/artifacts.py index 21d1f7a9..6cf82e70 100644 --- a/rest_api/rest_api_server/handlers/v2/profiling/artifacts.py +++ b/rest_api/rest_api_server/handlers/v2/profiling/artifacts.py @@ -5,7 +5,8 @@ ArtifactAsyncController) from rest_api.rest_api_server.handlers.v1.base_async import ( BaseAsyncCollectionHandler, BaseAsyncItemHandler) -from rest_api.rest_api_server.handlers.v1.base import BaseAuthHandler +from rest_api.rest_api_server.handlers.v1.base import ( + BaseAuthHandler, BaseAuthQueryTokenHandler) from rest_api.rest_api_server.handlers.v2.profiling.base import ( ProfilingHandler) from rest_api.rest_api_server.exceptions import Err @@ -50,8 +51,9 @@ def _validate_params(data, is_new=True): raise OptHTTPError.from_opt_exception(400, exc) -class ArtifactsAsyncCollectionHandler(BaseAsyncCollectionHandler, - BaseAuthHandler, ProfilingHandler): +class ArtifactsAsyncCollectionHandler( + BaseAsyncCollectionHandler, BaseAuthQueryTokenHandler, + ProfilingHandler): def _get_controller_class(self): return ArtifactAsyncController @@ -228,6 +230,13 @@ async def get(self, organization_id, **url_params): description: return artifacts starting from this number required: false type: integer + - name: token + in: query + description: | + Unique token related to organization profiling token + (only with run_id) + required: false + type: string responses: 200: description: Organization artifacts list @@ -293,11 +302,14 @@ async def get(self, organization_id, **url_params): security: - token: [] """ - await self.check_permissions( - 'INFO_ORGANIZATION', 'organization', organization_id) - token = await self._get_profiling_token(organization_id) + token = self.get_arg('token', str, None) params = self._get_query_params() - res = await run_task(self.controller.list, token, **params) + if not (await self.check_md5_profiling_token( + organization_id, token, raises=False) and params.get('run_id')): + await self.check_permissions( + 'INFO_ORGANIZATION', 'organization', organization_id) + profiling_token = await self._get_profiling_token(organization_id) + res = await run_task(self.controller.list, profiling_token, **params) self.write(json.dumps(res, cls=ModelEncoder)) diff --git a/rest_api/rest_api_server/handlers/v2/profiling/executors.py b/rest_api/rest_api_server/handlers/v2/profiling/executors.py index 6b42ea6c..76391033 100644 --- a/rest_api/rest_api_server/handlers/v2/profiling/executors.py +++ b/rest_api/rest_api_server/handlers/v2/profiling/executors.py @@ -145,10 +145,10 @@ async def get(self, organization_id, **url_params): organization_id, token, raises=False) or not run_ids: await self.check_permissions( 'INFO_ORGANIZATION', 'organization', organization_id) - token = await self._get_profiling_token(organization_id) + profiling_token = await self._get_profiling_token(organization_id) task_ids = self.get_arg('task_id', str, repeated=True) res = await run_task( - self.controller.list, organization_id, task_ids, token, + self.controller.list, organization_id, task_ids, profiling_token, run_ids=run_ids ) tasks_dict = {'executors': res} diff --git a/rest_api/rest_api_server/handlers/v2/profiling/runs.py b/rest_api/rest_api_server/handlers/v2/profiling/runs.py index 118d64ac..45181bb5 100644 --- a/rest_api/rest_api_server/handlers/v2/profiling/runs.py +++ b/rest_api/rest_api_server/handlers/v2/profiling/runs.py @@ -382,8 +382,9 @@ async def get(self, organization_id, id, **url_params): organization_id, token, raises=False): await self.check_permissions( 'INFO_ORGANIZATION', 'organization', organization_id) - token = await self._get_profiling_token(organization_id) - res = await run_task(self.controller.get, organization_id, id, token) + profiling_token = await self._get_profiling_token(organization_id) + res = await run_task(self.controller.get, organization_id, id, + profiling_token) self.write(json.dumps(res, cls=ModelEncoder)) async def delete(self, organization_id, id, **kwargs): diff --git a/rest_api/rest_api_server/tests/unittests/test_layouts.py b/rest_api/rest_api_server/tests/unittests/test_layouts.py index 42ca2276..bc32c692 100644 --- a/rest_api/rest_api_server/tests/unittests/test_layouts.py +++ b/rest_api/rest_api_server/tests/unittests/test_layouts.py @@ -31,6 +31,8 @@ def setUp(self, version="v2"): _, self.employee_org2 = self.client.employee_create( self.org2['id'], {'name': 'John Org2', 'auth_user_id': self.user_id2}) + _, resp = self.client.profiling_token_get(self.org['id']) + self.profiling_token = resp['token'] self.valid_layout = { 'name': 'layout', 'type': 'test', @@ -114,7 +116,7 @@ def test_list_layouts(self): type_ = 'test_type' layout0 = self.create_layout(owner_id=self.employee_org2['id'], type_=type_, shared=True) - layout1 = self.create_layout(owner_id=self.employee['id']) + self.create_layout(owner_id=self.employee['id']) layout2 = self.create_layout( type_=type_, owner_id=self.employee['id']) layout3 = self.create_layout( @@ -122,7 +124,7 @@ def test_list_layouts(self): layout4 = self.create_layout( owner_id=self.employee2['id'], entity_id=self.user_id, shared=True, type_='ml_run_charts_dashboard') - layout5 = self.create_layout( + self.create_layout( owner_id=self.employee2['id'], entity_id=self.user_id, shared=False, type_='ml_run_charts_dashboard') layout6 = self.create_layout( @@ -181,7 +183,7 @@ def test_list_layouts(self): # arcee token in query secret_p.return_value = False code, res = self.client.layouts_list( - self.org_id, token=self.get_profiling_token(self.org_id), + self.org_id, token=self.get_md5_token_hash(self.profiling_token), layout_type='ml_run_charts_dashboard', include_shared=True) self.assertEqual(code, 200) self.assertEqual(len(res['layouts']), 1) @@ -189,7 +191,8 @@ def test_list_layouts(self): code, res = self.client.layouts_list( self.org_id, layout_type='ml_run_charts_dashboard', - token=self.get_profiling_token(self.org_id), + token=self.get_md5_token_hash( + self.get_profiling_token(self.org_id)), include_shared=False) self.assertEqual(code, 200) self.assertEqual(len(res['layouts']), 0) @@ -264,6 +267,13 @@ def test_get_layout(self): self.assertEqual(code, 404) self.assertEqual(res['error']['error_code'], 'OE0002') + # by token + code, res = self.client.layout_get( + self.org_id, layout3['id'], + token=self.get_md5_token_hash(self.profiling_token)) + self.assertEqual(code, 200) + self.assertEqual(res['id'], layout3['id']) + def test_delete_layout(self): layout1 = self.create_layout(owner_id=self.employee['id']) code, _ = self.client.layout_delete( diff --git a/rest_api/rest_api_server/tests/unittests/test_profiling_artifacts.py b/rest_api/rest_api_server/tests/unittests/test_profiling_artifacts.py index e20352c3..5d581fd5 100644 --- a/rest_api/rest_api_server/tests/unittests/test_profiling_artifacts.py +++ b/rest_api/rest_api_server/tests/unittests/test_profiling_artifacts.py @@ -1,5 +1,7 @@ import uuid from unittest.mock import patch +from tools.optscale_exceptions.http_exc import OptHTTPError +from rest_api.rest_api_server.exceptions import Err from rest_api.rest_api_server.tests.unittests.test_profiling_base import ( TestProfilingBase) @@ -31,6 +33,8 @@ def setUp(self, version='v2'): 'description': 'Test description', 'tags': {'tag': 'tag'} } + _, resp = self.client.profiling_token_get(self.org['id']) + self.profiling_token = resp['token'] def test_create_artifact(self): code, resp = self.client.artifact_create( @@ -106,6 +110,46 @@ def test_list_artifacts(self): self.assertEqual(len(resp['artifacts']), 1) self.assertEqual(resp['artifacts'][0]['id'], artifact['id']) + def test_list_artifacts_by_token(self): + code, artifact = self.client.artifact_create( + self.org['id'], self.valid_artifact) + self.assertEqual(code, 201) + + def side_eff(_action, *_args, **_kwargs): + raise OptHTTPError(403, Err.OE0234, []) + + patch( + 'rest_api.rest_api_server.handlers.v1.base.' + 'BaseAuthHandler.check_permissions', + side_effect=side_eff).start() + + code, resp = self.client.artifacts_get( + self.org['id'], run_id=[self.valid_artifact['run_id']], + task_id=[self.task['id']], created_at_gt=0, + created_at_lt=artifact['created_at'] + 1, limit=1, start_from=0, + token=self.get_md5_token_hash(self.profiling_token) + ) + self.assertEqual(code, 200) + self.assertEqual(len(resp['artifacts']), 1) + self.assertEqual(resp['artifacts'][0]['id'], artifact['id']) + + code, resp = self.client.artifacts_get( + self.org['id'], task_id=[self.task['id']], created_at_gt=0, + created_at_lt=artifact['created_at'] + 1, limit=1, start_from=0, + token=self.get_md5_token_hash(self.profiling_token) + ) + self.assertEqual(code, 403) + self.assertEqual(resp['error']['error_code'], 'OE0234') + + code, resp = self.client.artifacts_get( + self.org['id'], run_id=[self.valid_artifact['run_id']], + task_id=[self.task['id']], created_at_gt=0, + created_at_lt=artifact['created_at'] + 1, limit=1, start_from=0, + token='123' + ) + self.assertEqual(code, 403) + self.assertEqual(resp['error']['error_code'], 'OE0234') + def test_list_artifacts_invalid_params(self): for param in ['created_at_gt', 'created_at_lt', 'start_from', 'limit']: code, resp = self.client.artifacts_get(self.org['id'], diff --git a/rest_api/rest_api_server/tests/unittests/test_profiling_base.py b/rest_api/rest_api_server/tests/unittests/test_profiling_base.py index 4c8f944b..3d278ae3 100644 --- a/rest_api/rest_api_server/tests/unittests/test_profiling_base.py +++ b/rest_api/rest_api_server/tests/unittests/test_profiling_base.py @@ -1,5 +1,6 @@ from datetime import datetime, timezone import uuid +import hashlib from typing import Optional, List from rest_api.rest_api_server.tests.unittests.test_api_base import TestApiBase @@ -36,6 +37,10 @@ def get_profiling_token(organization_id): if token: return token.token + @staticmethod + def get_md5_token_hash(profiling_token): + return hashlib.md5(profiling_token.encode('utf-8')).hexdigest() + def _gen_executor(self, token, **kwargs): executor_id = kwargs.pop('_id', None) if not executor_id: diff --git a/rest_api/rest_api_server/tests/unittests/test_profiling_executors.py b/rest_api/rest_api_server/tests/unittests/test_profiling_executors.py index 05d06796..daac047f 100644 --- a/rest_api/rest_api_server/tests/unittests/test_profiling_executors.py +++ b/rest_api/rest_api_server/tests/unittests/test_profiling_executors.py @@ -32,6 +32,8 @@ def setUp(self, version='v2'): } _, self.cloud_acc = self.create_cloud_account( self.org['id'], config, auth_user_id=self.user_id) + _, resp = self.client.profiling_token_get(self.org['id']) + self.profiling_token = resp['token'] def test_list_executors(self): code, task = self.client.task_create( @@ -271,3 +273,12 @@ def side_eff(_action, *_args, **_kwargs): self.org['id'], task['id'], token='123') self.assertEqual(code, 403) self.assertEqual(resp['error']['error_code'], 'OE0234') + + code, resp = self.client.executor_list( + self.org['id'], run_ids=r1['_id'], + token=self.get_md5_token_hash(self.profiling_token) + ) + self.assertEqual(code, 200) + self.assertEqual(len(resp['executors']), 1) + self.assertEqual(resp['executors'][0]['instance_id'], + valid_resource['cloud_resource_id']) diff --git a/rest_api/rest_api_server/tests/unittests/test_profiling_runs.py b/rest_api/rest_api_server/tests/unittests/test_profiling_runs.py index d6aab154..1d8310f0 100644 --- a/rest_api/rest_api_server/tests/unittests/test_profiling_runs.py +++ b/rest_api/rest_api_server/tests/unittests/test_profiling_runs.py @@ -29,6 +29,8 @@ def setUp(self, version='v2'): 'commit_id': "1fde95d5664ae9e542610993e17ee81b135b55c0", 'status': "dirty" } + _, resp = self.client.profiling_token_get(self.org['id']) + self.profiling_token = resp['token'] def test_get_run(self): code, resp = self.client.run_get(self.org['id'], '123') @@ -90,9 +92,8 @@ def test_get_run_with_token(self): 's3://ml-bucket/dataset', data={'step': 2000, 'loss': 55}, git=self.git_data) - token = self.get_profiling_token(self.org['id']) code, resp = self.client.run_get( - self.org['id'], run['_id'], token=token) + self.org['id'], run['_id']) self.assertEqual(code, 200) def side_eff(_action, *_args, **_kwargs): @@ -108,6 +109,11 @@ def side_eff(_action, *_args, **_kwargs): self.assertEqual(code, 403) self.assertEqual(resp['error']['error_code'], 'OE0234') + code, resp = self.client.run_get( + self.org['id'], run['_id'], + token=self.get_md5_token_hash(self.profiling_token)) + self.assertEqual(code, 200) + def test_get_run_console_data(self): metric_1 = self._create_metric(self.org['id'], 'loss') metric_2 = self._create_metric(self.org['id'], 'metric_2') @@ -581,7 +587,7 @@ def test_not_completed_run_tasks(self): self.cloud_resource_create_bulk( cloud_acc['id'], body, behavior='skip_existing', return_resources=True) - now_dt = datetime(2022, 5, 10) + now_dt = datetime(2022, 5, 10, tzinfo=timezone.utc) now = int(now_dt.timestamp()) code, task = self.client.task_create( self.org['id'], { @@ -653,22 +659,22 @@ def test_not_completed_run_cost(self): self.assertEqual(code, 200) raw_data = [ { - 'start_date': datetime(2022, 5, 15, 12), - 'end_date': datetime(2022, 5, 15, 16), + 'start_date': datetime(2022, 5, 15, 12, tzinfo=timezone.utc), + 'end_date': datetime(2022, 5, 15, 16, tzinfo=timezone.utc), 'cost': 120, 'cloud_account_id': cloud_acc['id'], 'resource_id': res_1['cloud_resource_id'] }, { - 'start_date': datetime(2022, 5, 15), - 'end_date': datetime(2022, 5, 16), + 'start_date': datetime(2022, 5, 15, tzinfo=timezone.utc), + 'end_date': datetime(2022, 5, 16, tzinfo=timezone.utc), 'cost': 179, 'cloud_account_id': cloud_acc['id'], 'resource_id': res_2['cloud_resource_id'] }, { - 'start_date': datetime(2022, 5, 15, 15), - 'end_date': datetime(2022, 5, 15, 16), + 'start_date': datetime(2022, 5, 15, 15, tzinfo=timezone.utc), + 'end_date': datetime(2022, 5, 15, 16, tzinfo=timezone.utc), 'identity/TimeInterval': '2017-11-01T00:00:00Z/2017-11-01T01:00:00Z', 'cost': 200, 'box_usage': True, @@ -694,12 +700,12 @@ def test_not_completed_run_cost(self): 'name': 'My test project', 'key': 'test_project', }) - with freeze_time(datetime(2022, 5, 16)): + with freeze_time(datetime(2022, 5, 16, tzinfo=timezone.utc)): self._create_run( self.org['id'], task['id'], [res_2['cloud_resource_id']], start=int(datetime( - 2022, 5, 15, 14).timestamp()), + 2022, 5, 15, 14, tzinfo=timezone.utc).timestamp()), finish=None, state=3) code, resp = self.client.task_get(self.org['id'], task['id']) self.assertEqual(code, 200) @@ -710,13 +716,13 @@ def test_not_completed_run_cost(self): self.org['id'], task['id'], [res_1['cloud_resource_id']], start=int(datetime( - 2022, 5, 15, 14).timestamp()), + 2022, 5, 15, 14, tzinfo=timezone.utc).timestamp()), finish=None, state=1) code, resp = self.client.task_get(self.org['id'], task['id']) self.assertEqual(code, 200) self.assertEqual(resp['last_run_cost'], 50) self.assertEqual(resp['last_run_duration'], 10 * 3600) - with freeze_time(datetime(2022, 5, 17)): + with freeze_time(datetime(2022, 5, 17, tzinfo=timezone.utc)): code, resp = self.client.task_get(self.org['id'], task['id']) self.assertEqual(code, 200) self.assertEqual(resp['last_run_cost'], 50 + 120)