diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe7fd487..9d7fa17d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: - name: Setup Python Version uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: 3.10.11 - name: Setup node for the whole package uses: actions/setup-node@v3 @@ -55,7 +55,7 @@ jobs: - name: Install Poetry Action uses: snok/install-poetry@v1 with: - version: 1.1.13 + version: 1.4.2 virtualenvs-create: true virtualenvs-in-project: true virtualenvs-path: backend/.venv @@ -73,6 +73,12 @@ jobs: dir: "vams/web" cmd: audit + - name: Web test + working-directory: vams/web + env: + CI: "true" + run: npm run test + - name: Web build working-directory: vams/web env: diff --git a/.gitignore b/.gitignore index 8e32ded0..6f3c6193 100644 --- a/.gitignore +++ b/.gitignore @@ -334,3 +334,4 @@ tags report +*.orig diff --git a/.prettierignore b/.prettierignore index 3a98fbae..107c74c4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,7 @@ build node_modules cdk.out -.mypy_cache \ No newline at end of file +.mypy_cache +.pytest_cache +ash_cf2cdk_output +ash diff --git a/backend/Dockerfile b/backend/Dockerfile index 00f6f33e..27a84093 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,7 +1,7 @@ # Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -FROM --platform=linux/amd64 public.ecr.aws/lambda/python:3.9 +FROM --platform=linux/amd64 public.ecr.aws/lambda/python:3.10 COPY requirements.txt . diff --git a/backend/backend/common/dynamodb.py b/backend/backend/common/dynamodb.py index ce0b3d11..09ba2770 100644 --- a/backend/backend/common/dynamodb.py +++ b/backend/backend/common/dynamodb.py @@ -1,8 +1,15 @@ # Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -def to_update_expr(record, op="SET"): - +from typing import Tuple +from typing import Any +from typing import Dict +def to_update_expr(record, op="SET") -> Tuple[Dict[str, str], Dict[str, Any], str]: + """ + :param record: + :param op: + :return: + """ keys = record.keys() keys_attr_names = ["#f{n}".format(n=x) for x in range(len(keys))] values_attr_names = [":v{n}".format(n=x) for x in range(len(keys))] @@ -19,4 +26,4 @@ def to_update_expr(record, op="SET"): "{f} = {v}".format(f=f, v=v) for f, v in zip(keys_attr_names, values_attr_names) ]) - return keys_map, values_map, expr + return keys_map, values_map, expr \ No newline at end of file diff --git a/backend/backend/handlers/auth/finegrainedaccessconstraints.py b/backend/backend/handlers/auth/finegrainedaccessconstraints.py new file mode 100644 index 00000000..2c58c9b4 --- /dev/null +++ b/backend/backend/handlers/auth/finegrainedaccessconstraints.py @@ -0,0 +1,166 @@ +# Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import json +from backend.handlers.auth import request_to_claims +import boto3 +import logging +import os +import traceback +from backend.logging.logger import safeLogger +from backend.common.dynamodb import to_update_expr +from boto3.dynamodb.conditions import Key, Attr + +logger = safeLogger(child=True, service="finegrainedpolicies", level="INFO") + +region = os.environ['AWS_REGION'] +dynamodb = boto3.resource('dynamodb', region_name=region) +table = dynamodb.Table(os.environ['TABLE_NAME']) + +attrs = "name,groupPermissions,constraintId,description,criteria".split(",") +keys_attrs = { "#{f}".format(f=f): f for f in attrs } + +class ValidationError(Exception): + def __init__(self, code: int, resp: object) -> None: + self.code = code + self.resp = resp + +def get_constraint(event, response): + + # db = boto3.client('dynamodb') + key, constraint = get_constraint_from_event(event) + + response['body'] = table.get_item( + Key=key, + ExpressionAttributeNames=keys_attrs, + ProjectionExpression=",".join(keys_attrs.keys()), + ) + response['body']['constraint'] = response['body']['Item'] + +def get_constraints(event, response): + result = table.query( + ExpressionAttributeNames=keys_attrs, + ProjectionExpression=",".join(keys_attrs.keys()), + KeyConditionExpression=Key('entityType').eq('constraint') & Key('sk').begins_with('constraint#'), + ) + logger.info( + msg="ddb response", + response=result + ) + response['body']['constraints'] = result['Items'] + + +# +# { +# "identifier": "constraintId", +# "name": "user defined name", +# "description": "description", +# "groupPermissions": [{ ... }] +# "created": "utc timestamp", +# "updated": "utc timestamp", +# "criteria": [ +# { +# "field": "fieldname", +# "operator": "contains", # one of contains, does not contain, is one of, is not one of +# "value": "value" # or ["value", "value"] +# } +# ] +# } +# + +def get_constraint_from_event(event): + constraint = None + if 'body' in event: + constraint = json.loads(event['body']) + + pathParameters = event.get('pathParameters', {}) + if 'constraintId' in pathParameters: + constraintId = pathParameters['constraintId'] + else: + constraintId = constraint['identifier'] + + key = { + 'entityType': 'constraint', + 'sk': 'constraint#' + constraintId, + } + return key, constraint + + +def update_constraint(event, response): + key, constraint = get_constraint_from_event(event) + keys_map, values_map, expr = to_update_expr(constraint) + + logger.info(msg={ + "keys_map": keys_map, + "values_map": values_map, + "expr": expr, + }) + + table.update_item( + Key=key, + UpdateExpression=expr, + ExpressionAttributeNames=keys_map, + ExpressionAttributeValues=values_map, + ReturnValues="UPDATED_NEW" + ) + + response['body']['constraint'] = constraint + + +def delete_constraint(event, response): + key, constraint = get_constraint_from_event(event) + table.delete_item( + Key=key + ) + response['body'] = { "message": "Constraint deleted." } + + +def lambda_handler(event, context): + + response = { + 'statusCode': 200, + 'body': { + "requestid": event['requestContext']['requestId'], + }, + } + + try: + claims_and_roles = request_to_claims(event) + + if "super-admin" not in claims_and_roles['roles']: + raise ValidationError(403, "Not Authorized") + + method = event['requestContext']['http']['method'] + pathParameters = event.get('pathParameters', {}) + + # For GET requests, retrieve the constraints from the table and return them as a json object + if method == 'GET' and 'constraintId' in pathParameters: + get_constraint(event, response) + + if method == 'GET' and 'constraintId' not in pathParameters: + get_constraints(event, response) + + # For POST requests, add the new constraint to the table and return the new constraint as a json object + if method == 'POST': + update_constraint(event, response) + + # For DELETE requests, remove the constraint from the table and return the deleted constraint as a json object + if method == 'DELETE': + delete_constraint(event, response) + + + response['body'] = json.dumps(response['body']) + return response + + except ValidationError as ex: + response['statusCode'] = ex.code + response['body']['error'] = ex.resp + response['body'] = json.dumps(response['body']) + return response + + except Exception as ex: + logger.error(traceback.format_exc(), event) + response['statusCode'] = 500 + response['body']['error'] = traceback.format_exc() + response['body'] = json.dumps(response['body']) + return response diff --git a/backend/backend/handlers/authz/__init__.py b/backend/backend/handlers/authz/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/backend/handlers/authz/opensearch.py b/backend/backend/handlers/authz/opensearch.py new file mode 100644 index 00000000..1a5f20fa --- /dev/null +++ b/backend/backend/handlers/authz/opensearch.py @@ -0,0 +1,124 @@ +# Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import boto3 +import os +from boto3.dynamodb.conditions import Key, Attr + + +class AuthEntities: + + def __init__(self, table): + self.table = table + + def all_constraints(self): + attrs = "name,groupPermissions,constraintId,description,criteria,entityType".split(",") + keys_attrs = {"#{f}".format(f=f): f for f in attrs} + # print(keys_attrs) + result = self.table.query( + # Limit=1, + ExpressionAttributeNames=keys_attrs, + ProjectionExpression=",".join(keys_attrs.keys()), + KeyConditionExpression=Key("entityType").eq("constraint"), + # FilterExpression=Attr("groupPermissions/groupId").eq(user_or_group) + ) + return result['Items'] + + def group_or_user_to_fine_grained_claims(self, groups): + constraints = self.all_constraints() + for item in constraints: + if len(groups & set([gp['groupId'] for gp in item['groupPermissions']])) > 0: + yield item + + def _format_one_of_criteria(self, criteria): + values = criteria['value'].split(",") + values = ["\"{}\"".format(s.strip()) for s in values] + values = " OR ".join(values) + return f"{criteria['field']}:({values})" + + def claims_to_opensearch_filters(self, claims, groups): + + by_operator = { + "contains": [], + "does_not_contain": [], + "is_one_of": [], + "is_not_one_of": [], + } + claim_predicates = [] + for claim in claims: + group_permission = [p for p in claim['groupPermissions'] if p['groupId'] in groups] + + predicates = [] + for criteria in claim['criteria']: + + if criteria['operator'] == "contains": + predicates.append(f"{criteria['field']}:({criteria['value']})") + + if criteria['operator'] == "does_not_contain": + predicates.append(f"-{criteria['field']}:({criteria['value']})") + + if criteria['operator'] == "is_one_of": + values_str = self._format_one_of_criteria(criteria) + predicates.append(f"{values_str}") + + if criteria['operator'] == "is_not_one_of": + values_str = self._format_one_of_criteria(criteria) + predicates.append(f"-{values_str}") + + claim_predicates.append("(" + " AND ".join(predicates) + ")") + + return { + "query": { + "query_string": { + "query": " OR ".join(claim_predicates) + } + } + } + + def claims_to_opensearch_agg(self, claims, groups): + + permissions = { + "Read": [], + "Edit": [], + "Admin": [] + } + for claim in claims: + group_permission = [p for p in claim['groupPermissions'] if p['groupId'] in groups] + + # The group permission structure is as follows: + # { + # "groupId": "group-id", + # "permissions": "PERMISSION" + # } + # Where PERMISSION is one of Read, Edit, Admin. + # A group can have only 1 permission in a set of groups in a claim. + # + # Aggregate the criteria for each group by the permission. + # + for permission in permissions.keys(): + for group in group_permission: + if group['permission'] == permission: + permissions[permission].append(claim) + + aggs = { + "aggs": { + "permissions": { + "filters": { + "filters": { + + } + } + } + } + } + + for permission in permissions.keys(): + query_string = self.claims_to_opensearch_filters(permissions[permission], groups)['query']['query_string'] + if query_string['query'] == "": + continue + + aggs["aggs"]["permissions"]["filters"]["filters"][permission] = { + "query_string": query_string + } + + return aggs diff --git a/backend/poetry.lock b/backend/poetry.lock index 0f9b3cad..62d8646a 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1,29 +1,23 @@ -[[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - [[package]] name = "attrs" -version = "22.1.0" +version = "22.2.0" description = "Classes Without Boilerplate" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"] +cov = ["attrs", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs"] +docs = ["furo", "sphinx", "myst-parser", "zope.interface", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["attrs", "zope.interface"] +tests-no-zope = ["hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist", "cloudpickle", "mypy (>=0.971,<0.990)", "pytest-mypy-plugins"] +tests_no_zope = ["hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist", "cloudpickle", "mypy (>=0.971,<0.990)", "pytest-mypy-plugins"] [[package]] name = "aws-lambda-powertools" -version = "2.7.1" -description = "A suite of utilities for AWS Lambda functions to ease adopting best practices such as tracing, structured logging, custom metrics, batching, idempotency, feature flags, and more." +version = "2.15.0" +description = "AWS Lambda Powertools is a developer toolkit to implement Serverless best practices and increase developer velocity." category = "main" optional = false python-versions = ">=3.7.4,<4.0.0" @@ -34,20 +28,20 @@ typing-extensions = ">=4.4.0,<5.0.0" [package.extras] tracer = ["aws-xray-sdk (>=2.8.0,<3.0.0)"] all = ["aws-xray-sdk (>=2.8.0,<3.0.0)", "fastjsonschema (>=2.14.5,<3.0.0)", "pydantic (>=1.8.2,<2.0.0)"] +aws-sdk = ["boto3 (>=1.20.32,<2.0.0)"] validation = ["fastjsonschema (>=2.14.5,<3.0.0)"] parser = ["pydantic (>=1.8.2,<2.0.0)"] -aws-sdk = ["boto3 (>=1.20.32,<2.0.0)"] [[package]] name = "boto3" -version = "1.25.5" +version = "1.26.128" description = "The AWS SDK for Python" category = "main" optional = false python-versions = ">= 3.7" [package.dependencies] -botocore = ">=1.28.5,<1.29.0" +botocore = ">=1.29.128,<1.30.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.6.0,<0.7.0" @@ -56,8 +50,8 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "boto3-stubs" -version = "1.26.81" -description = "Type annotations for boto3 1.26.81 generated with mypy-boto3-builder 7.12.4" +version = "1.26.128" +description = "Type annotations for boto3 1.26.128 generated with mypy-boto3-builder 7.14.5" category = "main" optional = false python-versions = ">=3.7" @@ -66,15 +60,14 @@ python-versions = ">=3.7" botocore-stubs = "*" mypy-boto3-stepfunctions = {version = ">=1.26.0,<1.27.0", optional = true, markers = "extra == \"stepfunctions\""} types-s3transfer = "*" -typing-extensions = ">=4.1.0" [package.extras] +all = ["mypy-boto3-clouddirectory (>=1.26.0,<1.27.0)", "mypy-boto3-cloudformation (>=1.26.0,<1.27.0)", "mypy-boto3-cloudfront (>=1.26.0,<1.27.0)", "mypy-boto3-cloudhsm (>=1.26.0,<1.27.0)", "mypy-boto3-cloudhsmv2 (>=1.26.0,<1.27.0)", "mypy-boto3-cloudsearch (>=1.26.0,<1.27.0)", "mypy-boto3-cloudsearchdomain (>=1.26.0,<1.27.0)", "mypy-boto3-cloudtrail (>=1.26.0,<1.27.0)", "mypy-boto3-accessanalyzer (>=1.26.0,<1.27.0)", "mypy-boto3-account (>=1.26.0,<1.27.0)", "mypy-boto3-acm (>=1.26.0,<1.27.0)", "mypy-boto3-acm-pca (>=1.26.0,<1.27.0)", "mypy-boto3-alexaforbusiness (>=1.26.0,<1.27.0)", "mypy-boto3-amp (>=1.26.0,<1.27.0)", "mypy-boto3-amplify (>=1.26.0,<1.27.0)", "mypy-boto3-amplifybackend (>=1.26.0,<1.27.0)", "mypy-boto3-amplifyuibuilder (>=1.26.0,<1.27.0)", "mypy-boto3-apigateway (>=1.26.0,<1.27.0)", "mypy-boto3-apigatewaymanagementapi (>=1.26.0,<1.27.0)", "mypy-boto3-apigatewayv2 (>=1.26.0,<1.27.0)", "mypy-boto3-appconfig (>=1.26.0,<1.27.0)", "mypy-boto3-appconfigdata (>=1.26.0,<1.27.0)", "mypy-boto3-appflow (>=1.26.0,<1.27.0)", "mypy-boto3-appintegrations (>=1.26.0,<1.27.0)", "mypy-boto3-application-autoscaling (>=1.26.0,<1.27.0)", "mypy-boto3-application-insights (>=1.26.0,<1.27.0)", "mypy-boto3-applicationcostprofiler (>=1.26.0,<1.27.0)", "mypy-boto3-appmesh (>=1.26.0,<1.27.0)", "mypy-boto3-apprunner (>=1.26.0,<1.27.0)", "mypy-boto3-appstream (>=1.26.0,<1.27.0)", "mypy-boto3-appsync (>=1.26.0,<1.27.0)", "mypy-boto3-arc-zonal-shift (>=1.26.0,<1.27.0)", "mypy-boto3-athena (>=1.26.0,<1.27.0)", "mypy-boto3-auditmanager (>=1.26.0,<1.27.0)", "mypy-boto3-autoscaling (>=1.26.0,<1.27.0)", "mypy-boto3-autoscaling-plans (>=1.26.0,<1.27.0)", "mypy-boto3-backup (>=1.26.0,<1.27.0)", "mypy-boto3-backup-gateway (>=1.26.0,<1.27.0)", "mypy-boto3-backupstorage (>=1.26.0,<1.27.0)", "mypy-boto3-batch (>=1.26.0,<1.27.0)", "mypy-boto3-billingconductor (>=1.26.0,<1.27.0)", "mypy-boto3-braket (>=1.26.0,<1.27.0)", "mypy-boto3-budgets (>=1.26.0,<1.27.0)", "mypy-boto3-ce (>=1.26.0,<1.27.0)", "mypy-boto3-chime (>=1.26.0,<1.27.0)", "mypy-boto3-chime-sdk-identity (>=1.26.0,<1.27.0)", "mypy-boto3-chime-sdk-media-pipelines (>=1.26.0,<1.27.0)", "mypy-boto3-chime-sdk-meetings (>=1.26.0,<1.27.0)", "mypy-boto3-chime-sdk-messaging (>=1.26.0,<1.27.0)", "mypy-boto3-chime-sdk-voice (>=1.26.0,<1.27.0)", "mypy-boto3-cleanrooms (>=1.26.0,<1.27.0)", "mypy-boto3-cloud9 (>=1.26.0,<1.27.0)", "mypy-boto3-cloudcontrol (>=1.26.0,<1.27.0)", "mypy-boto3-cloudtrail-data (>=1.26.0,<1.27.0)", "mypy-boto3-cloudwatch (>=1.26.0,<1.27.0)", "mypy-boto3-codeartifact (>=1.26.0,<1.27.0)", "mypy-boto3-codebuild (>=1.26.0,<1.27.0)", "mypy-boto3-codecatalyst (>=1.26.0,<1.27.0)", "mypy-boto3-codecommit (>=1.26.0,<1.27.0)", "mypy-boto3-codedeploy (>=1.26.0,<1.27.0)", "mypy-boto3-codeguru-reviewer (>=1.26.0,<1.27.0)", "mypy-boto3-codeguruprofiler (>=1.26.0,<1.27.0)", "mypy-boto3-codepipeline (>=1.26.0,<1.27.0)", "mypy-boto3-codestar (>=1.26.0,<1.27.0)", "mypy-boto3-codestar-connections (>=1.26.0,<1.27.0)", "mypy-boto3-codestar-notifications (>=1.26.0,<1.27.0)", "mypy-boto3-cognito-identity (>=1.26.0,<1.27.0)", "mypy-boto3-cognito-idp (>=1.26.0,<1.27.0)", "mypy-boto3-cognito-sync (>=1.26.0,<1.27.0)", "mypy-boto3-comprehend (>=1.26.0,<1.27.0)", "mypy-boto3-comprehendmedical (>=1.26.0,<1.27.0)", "mypy-boto3-compute-optimizer (>=1.26.0,<1.27.0)", "mypy-boto3-config (>=1.26.0,<1.27.0)", "mypy-boto3-connect (>=1.26.0,<1.27.0)", "mypy-boto3-connect-contact-lens (>=1.26.0,<1.27.0)", "mypy-boto3-connectcampaigns (>=1.26.0,<1.27.0)", "mypy-boto3-connectcases (>=1.26.0,<1.27.0)", "mypy-boto3-connectparticipant (>=1.26.0,<1.27.0)", "mypy-boto3-controltower (>=1.26.0,<1.27.0)", "mypy-boto3-cur (>=1.26.0,<1.27.0)", "mypy-boto3-customer-profiles (>=1.26.0,<1.27.0)", "mypy-boto3-databrew (>=1.26.0,<1.27.0)", "mypy-boto3-dataexchange (>=1.26.0,<1.27.0)", "mypy-boto3-datapipeline (>=1.26.0,<1.27.0)", "mypy-boto3-datasync (>=1.26.0,<1.27.0)", "mypy-boto3-dax (>=1.26.0,<1.27.0)", "mypy-boto3-detective (>=1.26.0,<1.27.0)", "mypy-boto3-devicefarm (>=1.26.0,<1.27.0)", "mypy-boto3-devops-guru (>=1.26.0,<1.27.0)", "mypy-boto3-directconnect (>=1.26.0,<1.27.0)", "mypy-boto3-discovery (>=1.26.0,<1.27.0)", "mypy-boto3-dlm (>=1.26.0,<1.27.0)", "mypy-boto3-dms (>=1.26.0,<1.27.0)", "mypy-boto3-docdb (>=1.26.0,<1.27.0)", "mypy-boto3-docdb-elastic (>=1.26.0,<1.27.0)", "mypy-boto3-drs (>=1.26.0,<1.27.0)", "mypy-boto3-ds (>=1.26.0,<1.27.0)", "mypy-boto3-dynamodb (>=1.26.0,<1.27.0)", "mypy-boto3-dynamodbstreams (>=1.26.0,<1.27.0)", "mypy-boto3-ebs (>=1.26.0,<1.27.0)", "mypy-boto3-ec2 (>=1.26.0,<1.27.0)", "mypy-boto3-ec2-instance-connect (>=1.26.0,<1.27.0)", "mypy-boto3-ecr (>=1.26.0,<1.27.0)", "mypy-boto3-ecr-public (>=1.26.0,<1.27.0)", "mypy-boto3-ecs (>=1.26.0,<1.27.0)", "mypy-boto3-efs (>=1.26.0,<1.27.0)", "mypy-boto3-eks (>=1.26.0,<1.27.0)", "mypy-boto3-elastic-inference (>=1.26.0,<1.27.0)", "mypy-boto3-elasticache (>=1.26.0,<1.27.0)", "mypy-boto3-elasticbeanstalk (>=1.26.0,<1.27.0)", "mypy-boto3-elastictranscoder (>=1.26.0,<1.27.0)", "mypy-boto3-elb (>=1.26.0,<1.27.0)", "mypy-boto3-elbv2 (>=1.26.0,<1.27.0)", "mypy-boto3-emr (>=1.26.0,<1.27.0)", "mypy-boto3-emr-containers (>=1.26.0,<1.27.0)", "mypy-boto3-emr-serverless (>=1.26.0,<1.27.0)", "mypy-boto3-es (>=1.26.0,<1.27.0)", "mypy-boto3-events (>=1.26.0,<1.27.0)", "mypy-boto3-evidently (>=1.26.0,<1.27.0)", "mypy-boto3-finspace (>=1.26.0,<1.27.0)", "mypy-boto3-finspace-data (>=1.26.0,<1.27.0)", "mypy-boto3-firehose (>=1.26.0,<1.27.0)", "mypy-boto3-fis (>=1.26.0,<1.27.0)", "mypy-boto3-fms (>=1.26.0,<1.27.0)", "mypy-boto3-forecast (>=1.26.0,<1.27.0)", "mypy-boto3-forecastquery (>=1.26.0,<1.27.0)", "mypy-boto3-frauddetector (>=1.26.0,<1.27.0)", "mypy-boto3-fsx (>=1.26.0,<1.27.0)", "mypy-boto3-gamelift (>=1.26.0,<1.27.0)", "mypy-boto3-gamesparks (>=1.26.0,<1.27.0)", "mypy-boto3-glacier (>=1.26.0,<1.27.0)", "mypy-boto3-globalaccelerator (>=1.26.0,<1.27.0)", "mypy-boto3-glue (>=1.26.0,<1.27.0)", "mypy-boto3-grafana (>=1.26.0,<1.27.0)", "mypy-boto3-greengrass (>=1.26.0,<1.27.0)", "mypy-boto3-greengrassv2 (>=1.26.0,<1.27.0)", "mypy-boto3-groundstation (>=1.26.0,<1.27.0)", "mypy-boto3-guardduty (>=1.26.0,<1.27.0)", "mypy-boto3-health (>=1.26.0,<1.27.0)", "mypy-boto3-healthlake (>=1.26.0,<1.27.0)", "mypy-boto3-honeycode (>=1.26.0,<1.27.0)", "mypy-boto3-iam (>=1.26.0,<1.27.0)", "mypy-boto3-identitystore (>=1.26.0,<1.27.0)", "mypy-boto3-imagebuilder (>=1.26.0,<1.27.0)", "mypy-boto3-importexport (>=1.26.0,<1.27.0)", "mypy-boto3-inspector (>=1.26.0,<1.27.0)", "mypy-boto3-inspector2 (>=1.26.0,<1.27.0)", "mypy-boto3-internetmonitor (>=1.26.0,<1.27.0)", "mypy-boto3-iot (>=1.26.0,<1.27.0)", "mypy-boto3-iot-data (>=1.26.0,<1.27.0)", "mypy-boto3-iot-jobs-data (>=1.26.0,<1.27.0)", "mypy-boto3-iot-roborunner (>=1.26.0,<1.27.0)", "mypy-boto3-iot1click-devices (>=1.26.0,<1.27.0)", "mypy-boto3-iot1click-projects (>=1.26.0,<1.27.0)", "mypy-boto3-iotanalytics (>=1.26.0,<1.27.0)", "mypy-boto3-iotdeviceadvisor (>=1.26.0,<1.27.0)", "mypy-boto3-iotevents (>=1.26.0,<1.27.0)", "mypy-boto3-iotevents-data (>=1.26.0,<1.27.0)", "mypy-boto3-iotfleethub (>=1.26.0,<1.27.0)", "mypy-boto3-iotfleetwise (>=1.26.0,<1.27.0)", "mypy-boto3-iotsecuretunneling (>=1.26.0,<1.27.0)", "mypy-boto3-iotsitewise (>=1.26.0,<1.27.0)", "mypy-boto3-iotthingsgraph (>=1.26.0,<1.27.0)", "mypy-boto3-iottwinmaker (>=1.26.0,<1.27.0)", "mypy-boto3-iotwireless (>=1.26.0,<1.27.0)", "mypy-boto3-ivs (>=1.26.0,<1.27.0)", "mypy-boto3-ivs-realtime (>=1.26.0,<1.27.0)", "mypy-boto3-ivschat (>=1.26.0,<1.27.0)", "mypy-boto3-kafka (>=1.26.0,<1.27.0)", "mypy-boto3-kafkaconnect (>=1.26.0,<1.27.0)", "mypy-boto3-kendra (>=1.26.0,<1.27.0)", "mypy-boto3-kendra-ranking (>=1.26.0,<1.27.0)", "mypy-boto3-keyspaces (>=1.26.0,<1.27.0)", "mypy-boto3-kinesis (>=1.26.0,<1.27.0)", "mypy-boto3-kinesis-video-archived-media (>=1.26.0,<1.27.0)", "mypy-boto3-kinesis-video-media (>=1.26.0,<1.27.0)", "mypy-boto3-kinesis-video-signaling (>=1.26.0,<1.27.0)", "mypy-boto3-kinesis-video-webrtc-storage (>=1.26.0,<1.27.0)", "mypy-boto3-kinesisanalytics (>=1.26.0,<1.27.0)", "mypy-boto3-kinesisanalyticsv2 (>=1.26.0,<1.27.0)", "mypy-boto3-kinesisvideo (>=1.26.0,<1.27.0)", "mypy-boto3-kms (>=1.26.0,<1.27.0)", "mypy-boto3-lakeformation (>=1.26.0,<1.27.0)", "mypy-boto3-lambda (>=1.26.0,<1.27.0)", "mypy-boto3-lex-models (>=1.26.0,<1.27.0)", "mypy-boto3-lex-runtime (>=1.26.0,<1.27.0)", "mypy-boto3-lexv2-models (>=1.26.0,<1.27.0)", "mypy-boto3-lexv2-runtime (>=1.26.0,<1.27.0)", "mypy-boto3-license-manager (>=1.26.0,<1.27.0)", "mypy-boto3-license-manager-linux-subscriptions (>=1.26.0,<1.27.0)", "mypy-boto3-license-manager-user-subscriptions (>=1.26.0,<1.27.0)", "mypy-boto3-lightsail (>=1.26.0,<1.27.0)", "mypy-boto3-location (>=1.26.0,<1.27.0)", "mypy-boto3-logs (>=1.26.0,<1.27.0)", "mypy-boto3-lookoutequipment (>=1.26.0,<1.27.0)", "mypy-boto3-lookoutmetrics (>=1.26.0,<1.27.0)", "mypy-boto3-lookoutvision (>=1.26.0,<1.27.0)", "mypy-boto3-m2 (>=1.26.0,<1.27.0)", "mypy-boto3-machinelearning (>=1.26.0,<1.27.0)", "mypy-boto3-macie (>=1.26.0,<1.27.0)", "mypy-boto3-macie2 (>=1.26.0,<1.27.0)", "mypy-boto3-managedblockchain (>=1.26.0,<1.27.0)", "mypy-boto3-marketplace-catalog (>=1.26.0,<1.27.0)", "mypy-boto3-marketplace-entitlement (>=1.26.0,<1.27.0)", "mypy-boto3-marketplacecommerceanalytics (>=1.26.0,<1.27.0)", "mypy-boto3-mediaconnect (>=1.26.0,<1.27.0)", "mypy-boto3-mediaconvert (>=1.26.0,<1.27.0)", "mypy-boto3-medialive (>=1.26.0,<1.27.0)", "mypy-boto3-mediapackage (>=1.26.0,<1.27.0)", "mypy-boto3-mediapackage-vod (>=1.26.0,<1.27.0)", "mypy-boto3-mediastore (>=1.26.0,<1.27.0)", "mypy-boto3-mediastore-data (>=1.26.0,<1.27.0)", "mypy-boto3-mediatailor (>=1.26.0,<1.27.0)", "mypy-boto3-memorydb (>=1.26.0,<1.27.0)", "mypy-boto3-meteringmarketplace (>=1.26.0,<1.27.0)", "mypy-boto3-mgh (>=1.26.0,<1.27.0)", "mypy-boto3-mgn (>=1.26.0,<1.27.0)", "mypy-boto3-migration-hub-refactor-spaces (>=1.26.0,<1.27.0)", "mypy-boto3-migrationhub-config (>=1.26.0,<1.27.0)", "mypy-boto3-migrationhuborchestrator (>=1.26.0,<1.27.0)", "mypy-boto3-migrationhubstrategy (>=1.26.0,<1.27.0)", "mypy-boto3-mobile (>=1.26.0,<1.27.0)", "mypy-boto3-mq (>=1.26.0,<1.27.0)", "mypy-boto3-mturk (>=1.26.0,<1.27.0)", "mypy-boto3-mwaa (>=1.26.0,<1.27.0)", "mypy-boto3-neptune (>=1.26.0,<1.27.0)", "mypy-boto3-network-firewall (>=1.26.0,<1.27.0)", "mypy-boto3-networkmanager (>=1.26.0,<1.27.0)", "mypy-boto3-nimble (>=1.26.0,<1.27.0)", "mypy-boto3-oam (>=1.26.0,<1.27.0)", "mypy-boto3-omics (>=1.26.0,<1.27.0)", "mypy-boto3-opensearch (>=1.26.0,<1.27.0)", "mypy-boto3-opensearchserverless (>=1.26.0,<1.27.0)", "mypy-boto3-opsworks (>=1.26.0,<1.27.0)", "mypy-boto3-opsworkscm (>=1.26.0,<1.27.0)", "mypy-boto3-organizations (>=1.26.0,<1.27.0)", "mypy-boto3-osis (>=1.26.0,<1.27.0)", "mypy-boto3-outposts (>=1.26.0,<1.27.0)", "mypy-boto3-panorama (>=1.26.0,<1.27.0)", "mypy-boto3-personalize (>=1.26.0,<1.27.0)", "mypy-boto3-personalize-events (>=1.26.0,<1.27.0)", "mypy-boto3-personalize-runtime (>=1.26.0,<1.27.0)", "mypy-boto3-pi (>=1.26.0,<1.27.0)", "mypy-boto3-pinpoint (>=1.26.0,<1.27.0)", "mypy-boto3-pinpoint-email (>=1.26.0,<1.27.0)", "mypy-boto3-pinpoint-sms-voice (>=1.26.0,<1.27.0)", "mypy-boto3-pinpoint-sms-voice-v2 (>=1.26.0,<1.27.0)", "mypy-boto3-pipes (>=1.26.0,<1.27.0)", "mypy-boto3-polly (>=1.26.0,<1.27.0)", "mypy-boto3-pricing (>=1.26.0,<1.27.0)", "mypy-boto3-privatenetworks (>=1.26.0,<1.27.0)", "mypy-boto3-proton (>=1.26.0,<1.27.0)", "mypy-boto3-qldb (>=1.26.0,<1.27.0)", "mypy-boto3-qldb-session (>=1.26.0,<1.27.0)", "mypy-boto3-quicksight (>=1.26.0,<1.27.0)", "mypy-boto3-ram (>=1.26.0,<1.27.0)", "mypy-boto3-rbin (>=1.26.0,<1.27.0)", "mypy-boto3-rds (>=1.26.0,<1.27.0)", "mypy-boto3-rds-data (>=1.26.0,<1.27.0)", "mypy-boto3-redshift (>=1.26.0,<1.27.0)", "mypy-boto3-redshift-data (>=1.26.0,<1.27.0)", "mypy-boto3-redshift-serverless (>=1.26.0,<1.27.0)", "mypy-boto3-rekognition (>=1.26.0,<1.27.0)", "mypy-boto3-resiliencehub (>=1.26.0,<1.27.0)", "mypy-boto3-resource-explorer-2 (>=1.26.0,<1.27.0)", "mypy-boto3-resource-groups (>=1.26.0,<1.27.0)", "mypy-boto3-resourcegroupstaggingapi (>=1.26.0,<1.27.0)", "mypy-boto3-robomaker (>=1.26.0,<1.27.0)", "mypy-boto3-rolesanywhere (>=1.26.0,<1.27.0)", "mypy-boto3-route53 (>=1.26.0,<1.27.0)", "mypy-boto3-route53-recovery-cluster (>=1.26.0,<1.27.0)", "mypy-boto3-route53-recovery-control-config (>=1.26.0,<1.27.0)", "mypy-boto3-route53-recovery-readiness (>=1.26.0,<1.27.0)", "mypy-boto3-route53domains (>=1.26.0,<1.27.0)", "mypy-boto3-route53resolver (>=1.26.0,<1.27.0)", "mypy-boto3-rum (>=1.26.0,<1.27.0)", "mypy-boto3-s3 (>=1.26.0,<1.27.0)", "mypy-boto3-s3control (>=1.26.0,<1.27.0)", "mypy-boto3-s3outposts (>=1.26.0,<1.27.0)", "mypy-boto3-sagemaker (>=1.26.0,<1.27.0)", "mypy-boto3-sagemaker-a2i-runtime (>=1.26.0,<1.27.0)", "mypy-boto3-sagemaker-edge (>=1.26.0,<1.27.0)", "mypy-boto3-sagemaker-featurestore-runtime (>=1.26.0,<1.27.0)", "mypy-boto3-sagemaker-geospatial (>=1.26.0,<1.27.0)", "mypy-boto3-sagemaker-metrics (>=1.26.0,<1.27.0)", "mypy-boto3-sagemaker-runtime (>=1.26.0,<1.27.0)", "mypy-boto3-savingsplans (>=1.26.0,<1.27.0)", "mypy-boto3-scheduler (>=1.26.0,<1.27.0)", "mypy-boto3-schemas (>=1.26.0,<1.27.0)", "mypy-boto3-sdb (>=1.26.0,<1.27.0)", "mypy-boto3-secretsmanager (>=1.26.0,<1.27.0)", "mypy-boto3-securityhub (>=1.26.0,<1.27.0)", "mypy-boto3-securitylake (>=1.26.0,<1.27.0)", "mypy-boto3-serverlessrepo (>=1.26.0,<1.27.0)", "mypy-boto3-service-quotas (>=1.26.0,<1.27.0)", "mypy-boto3-servicecatalog (>=1.26.0,<1.27.0)", "mypy-boto3-servicecatalog-appregistry (>=1.26.0,<1.27.0)", "mypy-boto3-servicediscovery (>=1.26.0,<1.27.0)", "mypy-boto3-ses (>=1.26.0,<1.27.0)", "mypy-boto3-sesv2 (>=1.26.0,<1.27.0)", "mypy-boto3-shield (>=1.26.0,<1.27.0)", "mypy-boto3-signer (>=1.26.0,<1.27.0)", "mypy-boto3-simspaceweaver (>=1.26.0,<1.27.0)", "mypy-boto3-sms (>=1.26.0,<1.27.0)", "mypy-boto3-sms-voice (>=1.26.0,<1.27.0)", "mypy-boto3-snow-device-management (>=1.26.0,<1.27.0)", "mypy-boto3-snowball (>=1.26.0,<1.27.0)", "mypy-boto3-sns (>=1.26.0,<1.27.0)", "mypy-boto3-sqs (>=1.26.0,<1.27.0)", "mypy-boto3-ssm (>=1.26.0,<1.27.0)", "mypy-boto3-ssm-contacts (>=1.26.0,<1.27.0)", "mypy-boto3-ssm-incidents (>=1.26.0,<1.27.0)", "mypy-boto3-ssm-sap (>=1.26.0,<1.27.0)", "mypy-boto3-sso (>=1.26.0,<1.27.0)", "mypy-boto3-sso-admin (>=1.26.0,<1.27.0)", "mypy-boto3-sso-oidc (>=1.26.0,<1.27.0)", "mypy-boto3-stepfunctions (>=1.26.0,<1.27.0)", "mypy-boto3-storagegateway (>=1.26.0,<1.27.0)", "mypy-boto3-sts (>=1.26.0,<1.27.0)", "mypy-boto3-support (>=1.26.0,<1.27.0)", "mypy-boto3-support-app (>=1.26.0,<1.27.0)", "mypy-boto3-swf (>=1.26.0,<1.27.0)", "mypy-boto3-synthetics (>=1.26.0,<1.27.0)", "mypy-boto3-textract (>=1.26.0,<1.27.0)", "mypy-boto3-timestream-query (>=1.26.0,<1.27.0)", "mypy-boto3-timestream-write (>=1.26.0,<1.27.0)", "mypy-boto3-tnb (>=1.26.0,<1.27.0)", "mypy-boto3-transcribe (>=1.26.0,<1.27.0)", "mypy-boto3-transfer (>=1.26.0,<1.27.0)", "mypy-boto3-translate (>=1.26.0,<1.27.0)", "mypy-boto3-voice-id (>=1.26.0,<1.27.0)", "mypy-boto3-vpc-lattice (>=1.26.0,<1.27.0)", "mypy-boto3-waf (>=1.26.0,<1.27.0)", "mypy-boto3-waf-regional (>=1.26.0,<1.27.0)", "mypy-boto3-wafv2 (>=1.26.0,<1.27.0)", "mypy-boto3-wellarchitected (>=1.26.0,<1.27.0)", "mypy-boto3-wisdom (>=1.26.0,<1.27.0)", "mypy-boto3-workdocs (>=1.26.0,<1.27.0)", "mypy-boto3-worklink (>=1.26.0,<1.27.0)", "mypy-boto3-workmail (>=1.26.0,<1.27.0)", "mypy-boto3-workmailmessageflow (>=1.26.0,<1.27.0)", "mypy-boto3-workspaces (>=1.26.0,<1.27.0)", "mypy-boto3-workspaces-web (>=1.26.0,<1.27.0)", "mypy-boto3-xray (>=1.26.0,<1.27.0)"] accessanalyzer = ["mypy-boto3-accessanalyzer (>=1.26.0,<1.27.0)"] account = ["mypy-boto3-account (>=1.26.0,<1.27.0)"] acm = ["mypy-boto3-acm (>=1.26.0,<1.27.0)"] acm-pca = ["mypy-boto3-acm-pca (>=1.26.0,<1.27.0)"] alexaforbusiness = ["mypy-boto3-alexaforbusiness (>=1.26.0,<1.27.0)"] -all = ["mypy-boto3-accessanalyzer (>=1.26.0,<1.27.0)", "mypy-boto3-account (>=1.26.0,<1.27.0)", "mypy-boto3-acm (>=1.26.0,<1.27.0)", "mypy-boto3-acm-pca (>=1.26.0,<1.27.0)", "mypy-boto3-alexaforbusiness (>=1.26.0,<1.27.0)", "mypy-boto3-amp (>=1.26.0,<1.27.0)", "mypy-boto3-amplify (>=1.26.0,<1.27.0)", "mypy-boto3-amplifybackend (>=1.26.0,<1.27.0)", "mypy-boto3-amplifyuibuilder (>=1.26.0,<1.27.0)", "mypy-boto3-apigateway (>=1.26.0,<1.27.0)", "mypy-boto3-apigatewaymanagementapi (>=1.26.0,<1.27.0)", "mypy-boto3-apigatewayv2 (>=1.26.0,<1.27.0)", "mypy-boto3-appconfig (>=1.26.0,<1.27.0)", "mypy-boto3-appconfigdata (>=1.26.0,<1.27.0)", "mypy-boto3-appflow (>=1.26.0,<1.27.0)", "mypy-boto3-appintegrations (>=1.26.0,<1.27.0)", "mypy-boto3-application-autoscaling (>=1.26.0,<1.27.0)", "mypy-boto3-application-insights (>=1.26.0,<1.27.0)", "mypy-boto3-applicationcostprofiler (>=1.26.0,<1.27.0)", "mypy-boto3-appmesh (>=1.26.0,<1.27.0)", "mypy-boto3-apprunner (>=1.26.0,<1.27.0)", "mypy-boto3-appstream (>=1.26.0,<1.27.0)", "mypy-boto3-appsync (>=1.26.0,<1.27.0)", "mypy-boto3-arc-zonal-shift (>=1.26.0,<1.27.0)", "mypy-boto3-athena (>=1.26.0,<1.27.0)", "mypy-boto3-auditmanager (>=1.26.0,<1.27.0)", "mypy-boto3-autoscaling (>=1.26.0,<1.27.0)", "mypy-boto3-autoscaling-plans (>=1.26.0,<1.27.0)", "mypy-boto3-backup (>=1.26.0,<1.27.0)", "mypy-boto3-backup-gateway (>=1.26.0,<1.27.0)", "mypy-boto3-backupstorage (>=1.26.0,<1.27.0)", "mypy-boto3-batch (>=1.26.0,<1.27.0)", "mypy-boto3-billingconductor (>=1.26.0,<1.27.0)", "mypy-boto3-braket (>=1.26.0,<1.27.0)", "mypy-boto3-budgets (>=1.26.0,<1.27.0)", "mypy-boto3-ce (>=1.26.0,<1.27.0)", "mypy-boto3-chime (>=1.26.0,<1.27.0)", "mypy-boto3-chime-sdk-identity (>=1.26.0,<1.27.0)", "mypy-boto3-chime-sdk-media-pipelines (>=1.26.0,<1.27.0)", "mypy-boto3-chime-sdk-meetings (>=1.26.0,<1.27.0)", "mypy-boto3-chime-sdk-messaging (>=1.26.0,<1.27.0)", "mypy-boto3-chime-sdk-voice (>=1.26.0,<1.27.0)", "mypy-boto3-cleanrooms (>=1.26.0,<1.27.0)", "mypy-boto3-cloud9 (>=1.26.0,<1.27.0)", "mypy-boto3-cloudcontrol (>=1.26.0,<1.27.0)", "mypy-boto3-clouddirectory (>=1.26.0,<1.27.0)", "mypy-boto3-cloudformation (>=1.26.0,<1.27.0)", "mypy-boto3-cloudfront (>=1.26.0,<1.27.0)", "mypy-boto3-cloudhsm (>=1.26.0,<1.27.0)", "mypy-boto3-cloudhsmv2 (>=1.26.0,<1.27.0)", "mypy-boto3-cloudsearch (>=1.26.0,<1.27.0)", "mypy-boto3-cloudsearchdomain (>=1.26.0,<1.27.0)", "mypy-boto3-cloudtrail (>=1.26.0,<1.27.0)", "mypy-boto3-cloudtrail-data (>=1.26.0,<1.27.0)", "mypy-boto3-cloudwatch (>=1.26.0,<1.27.0)", "mypy-boto3-codeartifact (>=1.26.0,<1.27.0)", "mypy-boto3-codebuild (>=1.26.0,<1.27.0)", "mypy-boto3-codecatalyst (>=1.26.0,<1.27.0)", "mypy-boto3-codecommit (>=1.26.0,<1.27.0)", "mypy-boto3-codedeploy (>=1.26.0,<1.27.0)", "mypy-boto3-codeguru-reviewer (>=1.26.0,<1.27.0)", "mypy-boto3-codeguruprofiler (>=1.26.0,<1.27.0)", "mypy-boto3-codepipeline (>=1.26.0,<1.27.0)", "mypy-boto3-codestar (>=1.26.0,<1.27.0)", "mypy-boto3-codestar-connections (>=1.26.0,<1.27.0)", "mypy-boto3-codestar-notifications (>=1.26.0,<1.27.0)", "mypy-boto3-cognito-identity (>=1.26.0,<1.27.0)", "mypy-boto3-cognito-idp (>=1.26.0,<1.27.0)", "mypy-boto3-cognito-sync (>=1.26.0,<1.27.0)", "mypy-boto3-comprehend (>=1.26.0,<1.27.0)", "mypy-boto3-comprehendmedical (>=1.26.0,<1.27.0)", "mypy-boto3-compute-optimizer (>=1.26.0,<1.27.0)", "mypy-boto3-config (>=1.26.0,<1.27.0)", "mypy-boto3-connect (>=1.26.0,<1.27.0)", "mypy-boto3-connect-contact-lens (>=1.26.0,<1.27.0)", "mypy-boto3-connectcampaigns (>=1.26.0,<1.27.0)", "mypy-boto3-connectcases (>=1.26.0,<1.27.0)", "mypy-boto3-connectparticipant (>=1.26.0,<1.27.0)", "mypy-boto3-controltower (>=1.26.0,<1.27.0)", "mypy-boto3-cur (>=1.26.0,<1.27.0)", "mypy-boto3-customer-profiles (>=1.26.0,<1.27.0)", "mypy-boto3-databrew (>=1.26.0,<1.27.0)", "mypy-boto3-dataexchange (>=1.26.0,<1.27.0)", "mypy-boto3-datapipeline (>=1.26.0,<1.27.0)", "mypy-boto3-datasync (>=1.26.0,<1.27.0)", "mypy-boto3-dax (>=1.26.0,<1.27.0)", "mypy-boto3-detective (>=1.26.0,<1.27.0)", "mypy-boto3-devicefarm (>=1.26.0,<1.27.0)", "mypy-boto3-devops-guru (>=1.26.0,<1.27.0)", "mypy-boto3-directconnect (>=1.26.0,<1.27.0)", "mypy-boto3-discovery (>=1.26.0,<1.27.0)", "mypy-boto3-dlm (>=1.26.0,<1.27.0)", "mypy-boto3-dms (>=1.26.0,<1.27.0)", "mypy-boto3-docdb (>=1.26.0,<1.27.0)", "mypy-boto3-docdb-elastic (>=1.26.0,<1.27.0)", "mypy-boto3-drs (>=1.26.0,<1.27.0)", "mypy-boto3-ds (>=1.26.0,<1.27.0)", "mypy-boto3-dynamodb (>=1.26.0,<1.27.0)", "mypy-boto3-dynamodbstreams (>=1.26.0,<1.27.0)", "mypy-boto3-ebs (>=1.26.0,<1.27.0)", "mypy-boto3-ec2 (>=1.26.0,<1.27.0)", "mypy-boto3-ec2-instance-connect (>=1.26.0,<1.27.0)", "mypy-boto3-ecr (>=1.26.0,<1.27.0)", "mypy-boto3-ecr-public (>=1.26.0,<1.27.0)", "mypy-boto3-ecs (>=1.26.0,<1.27.0)", "mypy-boto3-efs (>=1.26.0,<1.27.0)", "mypy-boto3-eks (>=1.26.0,<1.27.0)", "mypy-boto3-elastic-inference (>=1.26.0,<1.27.0)", "mypy-boto3-elasticache (>=1.26.0,<1.27.0)", "mypy-boto3-elasticbeanstalk (>=1.26.0,<1.27.0)", "mypy-boto3-elastictranscoder (>=1.26.0,<1.27.0)", "mypy-boto3-elb (>=1.26.0,<1.27.0)", "mypy-boto3-elbv2 (>=1.26.0,<1.27.0)", "mypy-boto3-emr (>=1.26.0,<1.27.0)", "mypy-boto3-emr-containers (>=1.26.0,<1.27.0)", "mypy-boto3-emr-serverless (>=1.26.0,<1.27.0)", "mypy-boto3-es (>=1.26.0,<1.27.0)", "mypy-boto3-events (>=1.26.0,<1.27.0)", "mypy-boto3-evidently (>=1.26.0,<1.27.0)", "mypy-boto3-finspace (>=1.26.0,<1.27.0)", "mypy-boto3-finspace-data (>=1.26.0,<1.27.0)", "mypy-boto3-firehose (>=1.26.0,<1.27.0)", "mypy-boto3-fis (>=1.26.0,<1.27.0)", "mypy-boto3-fms (>=1.26.0,<1.27.0)", "mypy-boto3-forecast (>=1.26.0,<1.27.0)", "mypy-boto3-forecastquery (>=1.26.0,<1.27.0)", "mypy-boto3-frauddetector (>=1.26.0,<1.27.0)", "mypy-boto3-fsx (>=1.26.0,<1.27.0)", "mypy-boto3-gamelift (>=1.26.0,<1.27.0)", "mypy-boto3-gamesparks (>=1.26.0,<1.27.0)", "mypy-boto3-glacier (>=1.26.0,<1.27.0)", "mypy-boto3-globalaccelerator (>=1.26.0,<1.27.0)", "mypy-boto3-glue (>=1.26.0,<1.27.0)", "mypy-boto3-grafana (>=1.26.0,<1.27.0)", "mypy-boto3-greengrass (>=1.26.0,<1.27.0)", "mypy-boto3-greengrassv2 (>=1.26.0,<1.27.0)", "mypy-boto3-groundstation (>=1.26.0,<1.27.0)", "mypy-boto3-guardduty (>=1.26.0,<1.27.0)", "mypy-boto3-health (>=1.26.0,<1.27.0)", "mypy-boto3-healthlake (>=1.26.0,<1.27.0)", "mypy-boto3-honeycode (>=1.26.0,<1.27.0)", "mypy-boto3-iam (>=1.26.0,<1.27.0)", "mypy-boto3-identitystore (>=1.26.0,<1.27.0)", "mypy-boto3-imagebuilder (>=1.26.0,<1.27.0)", "mypy-boto3-importexport (>=1.26.0,<1.27.0)", "mypy-boto3-inspector (>=1.26.0,<1.27.0)", "mypy-boto3-inspector2 (>=1.26.0,<1.27.0)", "mypy-boto3-internetmonitor (>=1.26.0,<1.27.0)", "mypy-boto3-iot (>=1.26.0,<1.27.0)", "mypy-boto3-iot-data (>=1.26.0,<1.27.0)", "mypy-boto3-iot-jobs-data (>=1.26.0,<1.27.0)", "mypy-boto3-iot-roborunner (>=1.26.0,<1.27.0)", "mypy-boto3-iot1click-devices (>=1.26.0,<1.27.0)", "mypy-boto3-iot1click-projects (>=1.26.0,<1.27.0)", "mypy-boto3-iotanalytics (>=1.26.0,<1.27.0)", "mypy-boto3-iotdeviceadvisor (>=1.26.0,<1.27.0)", "mypy-boto3-iotevents (>=1.26.0,<1.27.0)", "mypy-boto3-iotevents-data (>=1.26.0,<1.27.0)", "mypy-boto3-iotfleethub (>=1.26.0,<1.27.0)", "mypy-boto3-iotfleetwise (>=1.26.0,<1.27.0)", "mypy-boto3-iotsecuretunneling (>=1.26.0,<1.27.0)", "mypy-boto3-iotsitewise (>=1.26.0,<1.27.0)", "mypy-boto3-iotthingsgraph (>=1.26.0,<1.27.0)", "mypy-boto3-iottwinmaker (>=1.26.0,<1.27.0)", "mypy-boto3-iotwireless (>=1.26.0,<1.27.0)", "mypy-boto3-ivs (>=1.26.0,<1.27.0)", "mypy-boto3-ivschat (>=1.26.0,<1.27.0)", "mypy-boto3-kafka (>=1.26.0,<1.27.0)", "mypy-boto3-kafkaconnect (>=1.26.0,<1.27.0)", "mypy-boto3-kendra (>=1.26.0,<1.27.0)", "mypy-boto3-kendra-ranking (>=1.26.0,<1.27.0)", "mypy-boto3-keyspaces (>=1.26.0,<1.27.0)", "mypy-boto3-kinesis (>=1.26.0,<1.27.0)", "mypy-boto3-kinesis-video-archived-media (>=1.26.0,<1.27.0)", "mypy-boto3-kinesis-video-media (>=1.26.0,<1.27.0)", "mypy-boto3-kinesis-video-signaling (>=1.26.0,<1.27.0)", "mypy-boto3-kinesis-video-webrtc-storage (>=1.26.0,<1.27.0)", "mypy-boto3-kinesisanalytics (>=1.26.0,<1.27.0)", "mypy-boto3-kinesisanalyticsv2 (>=1.26.0,<1.27.0)", "mypy-boto3-kinesisvideo (>=1.26.0,<1.27.0)", "mypy-boto3-kms (>=1.26.0,<1.27.0)", "mypy-boto3-lakeformation (>=1.26.0,<1.27.0)", "mypy-boto3-lambda (>=1.26.0,<1.27.0)", "mypy-boto3-lex-models (>=1.26.0,<1.27.0)", "mypy-boto3-lex-runtime (>=1.26.0,<1.27.0)", "mypy-boto3-lexv2-models (>=1.26.0,<1.27.0)", "mypy-boto3-lexv2-runtime (>=1.26.0,<1.27.0)", "mypy-boto3-license-manager (>=1.26.0,<1.27.0)", "mypy-boto3-license-manager-linux-subscriptions (>=1.26.0,<1.27.0)", "mypy-boto3-license-manager-user-subscriptions (>=1.26.0,<1.27.0)", "mypy-boto3-lightsail (>=1.26.0,<1.27.0)", "mypy-boto3-location (>=1.26.0,<1.27.0)", "mypy-boto3-logs (>=1.26.0,<1.27.0)", "mypy-boto3-lookoutequipment (>=1.26.0,<1.27.0)", "mypy-boto3-lookoutmetrics (>=1.26.0,<1.27.0)", "mypy-boto3-lookoutvision (>=1.26.0,<1.27.0)", "mypy-boto3-m2 (>=1.26.0,<1.27.0)", "mypy-boto3-machinelearning (>=1.26.0,<1.27.0)", "mypy-boto3-macie (>=1.26.0,<1.27.0)", "mypy-boto3-macie2 (>=1.26.0,<1.27.0)", "mypy-boto3-managedblockchain (>=1.26.0,<1.27.0)", "mypy-boto3-marketplace-catalog (>=1.26.0,<1.27.0)", "mypy-boto3-marketplace-entitlement (>=1.26.0,<1.27.0)", "mypy-boto3-marketplacecommerceanalytics (>=1.26.0,<1.27.0)", "mypy-boto3-mediaconnect (>=1.26.0,<1.27.0)", "mypy-boto3-mediaconvert (>=1.26.0,<1.27.0)", "mypy-boto3-medialive (>=1.26.0,<1.27.0)", "mypy-boto3-mediapackage (>=1.26.0,<1.27.0)", "mypy-boto3-mediapackage-vod (>=1.26.0,<1.27.0)", "mypy-boto3-mediastore (>=1.26.0,<1.27.0)", "mypy-boto3-mediastore-data (>=1.26.0,<1.27.0)", "mypy-boto3-mediatailor (>=1.26.0,<1.27.0)", "mypy-boto3-memorydb (>=1.26.0,<1.27.0)", "mypy-boto3-meteringmarketplace (>=1.26.0,<1.27.0)", "mypy-boto3-mgh (>=1.26.0,<1.27.0)", "mypy-boto3-mgn (>=1.26.0,<1.27.0)", "mypy-boto3-migration-hub-refactor-spaces (>=1.26.0,<1.27.0)", "mypy-boto3-migrationhub-config (>=1.26.0,<1.27.0)", "mypy-boto3-migrationhuborchestrator (>=1.26.0,<1.27.0)", "mypy-boto3-migrationhubstrategy (>=1.26.0,<1.27.0)", "mypy-boto3-mobile (>=1.26.0,<1.27.0)", "mypy-boto3-mq (>=1.26.0,<1.27.0)", "mypy-boto3-mturk (>=1.26.0,<1.27.0)", "mypy-boto3-mwaa (>=1.26.0,<1.27.0)", "mypy-boto3-neptune (>=1.26.0,<1.27.0)", "mypy-boto3-network-firewall (>=1.26.0,<1.27.0)", "mypy-boto3-networkmanager (>=1.26.0,<1.27.0)", "mypy-boto3-nimble (>=1.26.0,<1.27.0)", "mypy-boto3-oam (>=1.26.0,<1.27.0)", "mypy-boto3-omics (>=1.26.0,<1.27.0)", "mypy-boto3-opensearch (>=1.26.0,<1.27.0)", "mypy-boto3-opensearchserverless (>=1.26.0,<1.27.0)", "mypy-boto3-opsworks (>=1.26.0,<1.27.0)", "mypy-boto3-opsworkscm (>=1.26.0,<1.27.0)", "mypy-boto3-organizations (>=1.26.0,<1.27.0)", "mypy-boto3-outposts (>=1.26.0,<1.27.0)", "mypy-boto3-panorama (>=1.26.0,<1.27.0)", "mypy-boto3-personalize (>=1.26.0,<1.27.0)", "mypy-boto3-personalize-events (>=1.26.0,<1.27.0)", "mypy-boto3-personalize-runtime (>=1.26.0,<1.27.0)", "mypy-boto3-pi (>=1.26.0,<1.27.0)", "mypy-boto3-pinpoint (>=1.26.0,<1.27.0)", "mypy-boto3-pinpoint-email (>=1.26.0,<1.27.0)", "mypy-boto3-pinpoint-sms-voice (>=1.26.0,<1.27.0)", "mypy-boto3-pinpoint-sms-voice-v2 (>=1.26.0,<1.27.0)", "mypy-boto3-pipes (>=1.26.0,<1.27.0)", "mypy-boto3-polly (>=1.26.0,<1.27.0)", "mypy-boto3-pricing (>=1.26.0,<1.27.0)", "mypy-boto3-privatenetworks (>=1.26.0,<1.27.0)", "mypy-boto3-proton (>=1.26.0,<1.27.0)", "mypy-boto3-qldb (>=1.26.0,<1.27.0)", "mypy-boto3-qldb-session (>=1.26.0,<1.27.0)", "mypy-boto3-quicksight (>=1.26.0,<1.27.0)", "mypy-boto3-ram (>=1.26.0,<1.27.0)", "mypy-boto3-rbin (>=1.26.0,<1.27.0)", "mypy-boto3-rds (>=1.26.0,<1.27.0)", "mypy-boto3-rds-data (>=1.26.0,<1.27.0)", "mypy-boto3-redshift (>=1.26.0,<1.27.0)", "mypy-boto3-redshift-data (>=1.26.0,<1.27.0)", "mypy-boto3-redshift-serverless (>=1.26.0,<1.27.0)", "mypy-boto3-rekognition (>=1.26.0,<1.27.0)", "mypy-boto3-resiliencehub (>=1.26.0,<1.27.0)", "mypy-boto3-resource-explorer-2 (>=1.26.0,<1.27.0)", "mypy-boto3-resource-groups (>=1.26.0,<1.27.0)", "mypy-boto3-resourcegroupstaggingapi (>=1.26.0,<1.27.0)", "mypy-boto3-robomaker (>=1.26.0,<1.27.0)", "mypy-boto3-rolesanywhere (>=1.26.0,<1.27.0)", "mypy-boto3-route53 (>=1.26.0,<1.27.0)", "mypy-boto3-route53-recovery-cluster (>=1.26.0,<1.27.0)", "mypy-boto3-route53-recovery-control-config (>=1.26.0,<1.27.0)", "mypy-boto3-route53-recovery-readiness (>=1.26.0,<1.27.0)", "mypy-boto3-route53domains (>=1.26.0,<1.27.0)", "mypy-boto3-route53resolver (>=1.26.0,<1.27.0)", "mypy-boto3-rum (>=1.26.0,<1.27.0)", "mypy-boto3-s3 (>=1.26.0,<1.27.0)", "mypy-boto3-s3control (>=1.26.0,<1.27.0)", "mypy-boto3-s3outposts (>=1.26.0,<1.27.0)", "mypy-boto3-sagemaker (>=1.26.0,<1.27.0)", "mypy-boto3-sagemaker-a2i-runtime (>=1.26.0,<1.27.0)", "mypy-boto3-sagemaker-edge (>=1.26.0,<1.27.0)", "mypy-boto3-sagemaker-featurestore-runtime (>=1.26.0,<1.27.0)", "mypy-boto3-sagemaker-geospatial (>=1.26.0,<1.27.0)", "mypy-boto3-sagemaker-metrics (>=1.26.0,<1.27.0)", "mypy-boto3-sagemaker-runtime (>=1.26.0,<1.27.0)", "mypy-boto3-savingsplans (>=1.26.0,<1.27.0)", "mypy-boto3-scheduler (>=1.26.0,<1.27.0)", "mypy-boto3-schemas (>=1.26.0,<1.27.0)", "mypy-boto3-sdb (>=1.26.0,<1.27.0)", "mypy-boto3-secretsmanager (>=1.26.0,<1.27.0)", "mypy-boto3-securityhub (>=1.26.0,<1.27.0)", "mypy-boto3-securitylake (>=1.26.0,<1.27.0)", "mypy-boto3-serverlessrepo (>=1.26.0,<1.27.0)", "mypy-boto3-service-quotas (>=1.26.0,<1.27.0)", "mypy-boto3-servicecatalog (>=1.26.0,<1.27.0)", "mypy-boto3-servicecatalog-appregistry (>=1.26.0,<1.27.0)", "mypy-boto3-servicediscovery (>=1.26.0,<1.27.0)", "mypy-boto3-ses (>=1.26.0,<1.27.0)", "mypy-boto3-sesv2 (>=1.26.0,<1.27.0)", "mypy-boto3-shield (>=1.26.0,<1.27.0)", "mypy-boto3-signer (>=1.26.0,<1.27.0)", "mypy-boto3-simspaceweaver (>=1.26.0,<1.27.0)", "mypy-boto3-sms (>=1.26.0,<1.27.0)", "mypy-boto3-sms-voice (>=1.26.0,<1.27.0)", "mypy-boto3-snow-device-management (>=1.26.0,<1.27.0)", "mypy-boto3-snowball (>=1.26.0,<1.27.0)", "mypy-boto3-sns (>=1.26.0,<1.27.0)", "mypy-boto3-sqs (>=1.26.0,<1.27.0)", "mypy-boto3-ssm (>=1.26.0,<1.27.0)", "mypy-boto3-ssm-contacts (>=1.26.0,<1.27.0)", "mypy-boto3-ssm-incidents (>=1.26.0,<1.27.0)", "mypy-boto3-ssm-sap (>=1.26.0,<1.27.0)", "mypy-boto3-sso (>=1.26.0,<1.27.0)", "mypy-boto3-sso-admin (>=1.26.0,<1.27.0)", "mypy-boto3-sso-oidc (>=1.26.0,<1.27.0)", "mypy-boto3-stepfunctions (>=1.26.0,<1.27.0)", "mypy-boto3-storagegateway (>=1.26.0,<1.27.0)", "mypy-boto3-sts (>=1.26.0,<1.27.0)", "mypy-boto3-support (>=1.26.0,<1.27.0)", "mypy-boto3-support-app (>=1.26.0,<1.27.0)", "mypy-boto3-swf (>=1.26.0,<1.27.0)", "mypy-boto3-synthetics (>=1.26.0,<1.27.0)", "mypy-boto3-textract (>=1.26.0,<1.27.0)", "mypy-boto3-timestream-query (>=1.26.0,<1.27.0)", "mypy-boto3-timestream-write (>=1.26.0,<1.27.0)", "mypy-boto3-tnb (>=1.26.0,<1.27.0)", "mypy-boto3-transcribe (>=1.26.0,<1.27.0)", "mypy-boto3-transfer (>=1.26.0,<1.27.0)", "mypy-boto3-translate (>=1.26.0,<1.27.0)", "mypy-boto3-voice-id (>=1.26.0,<1.27.0)", "mypy-boto3-waf (>=1.26.0,<1.27.0)", "mypy-boto3-waf-regional (>=1.26.0,<1.27.0)", "mypy-boto3-wafv2 (>=1.26.0,<1.27.0)", "mypy-boto3-wellarchitected (>=1.26.0,<1.27.0)", "mypy-boto3-wisdom (>=1.26.0,<1.27.0)", "mypy-boto3-workdocs (>=1.26.0,<1.27.0)", "mypy-boto3-worklink (>=1.26.0,<1.27.0)", "mypy-boto3-workmail (>=1.26.0,<1.27.0)", "mypy-boto3-workmailmessageflow (>=1.26.0,<1.27.0)", "mypy-boto3-workspaces (>=1.26.0,<1.27.0)", "mypy-boto3-workspaces-web (>=1.26.0,<1.27.0)", "mypy-boto3-xray (>=1.26.0,<1.27.0)"] amp = ["mypy-boto3-amp (>=1.26.0,<1.27.0)"] amplify = ["mypy-boto3-amplify (>=1.26.0,<1.27.0)"] amplifybackend = ["mypy-boto3-amplifybackend (>=1.26.0,<1.27.0)"] @@ -103,7 +96,7 @@ backup-gateway = ["mypy-boto3-backup-gateway (>=1.26.0,<1.27.0)"] backupstorage = ["mypy-boto3-backupstorage (>=1.26.0,<1.27.0)"] batch = ["mypy-boto3-batch (>=1.26.0,<1.27.0)"] billingconductor = ["mypy-boto3-billingconductor (>=1.26.0,<1.27.0)"] -boto3 = ["boto3 (==1.26.81)", "botocore (==1.29.81)"] +boto3 = ["boto3 (==1.26.128)", "botocore (==1.29.128)"] braket = ["mypy-boto3-braket (>=1.26.0,<1.27.0)"] budgets = ["mypy-boto3-budgets (>=1.26.0,<1.27.0)"] ce = ["mypy-boto3-ce (>=1.26.0,<1.27.0)"] @@ -238,6 +231,7 @@ iotthingsgraph = ["mypy-boto3-iotthingsgraph (>=1.26.0,<1.27.0)"] iottwinmaker = ["mypy-boto3-iottwinmaker (>=1.26.0,<1.27.0)"] iotwireless = ["mypy-boto3-iotwireless (>=1.26.0,<1.27.0)"] ivs = ["mypy-boto3-ivs (>=1.26.0,<1.27.0)"] +ivs-realtime = ["mypy-boto3-ivs-realtime (>=1.26.0,<1.27.0)"] ivschat = ["mypy-boto3-ivschat (>=1.26.0,<1.27.0)"] kafka = ["mypy-boto3-kafka (>=1.26.0,<1.27.0)"] kafkaconnect = ["mypy-boto3-kafkaconnect (>=1.26.0,<1.27.0)"] @@ -307,6 +301,7 @@ opensearchserverless = ["mypy-boto3-opensearchserverless (>=1.26.0,<1.27.0)"] opsworks = ["mypy-boto3-opsworks (>=1.26.0,<1.27.0)"] opsworkscm = ["mypy-boto3-opsworkscm (>=1.26.0,<1.27.0)"] organizations = ["mypy-boto3-organizations (>=1.26.0,<1.27.0)"] +osis = ["mypy-boto3-osis (>=1.26.0,<1.27.0)"] outposts = ["mypy-boto3-outposts (>=1.26.0,<1.27.0)"] panorama = ["mypy-boto3-panorama (>=1.26.0,<1.27.0)"] personalize = ["mypy-boto3-personalize (>=1.26.0,<1.27.0)"] @@ -401,6 +396,7 @@ transcribe = ["mypy-boto3-transcribe (>=1.26.0,<1.27.0)"] transfer = ["mypy-boto3-transfer (>=1.26.0,<1.27.0)"] translate = ["mypy-boto3-translate (>=1.26.0,<1.27.0)"] voice-id = ["mypy-boto3-voice-id (>=1.26.0,<1.27.0)"] +vpc-lattice = ["mypy-boto3-vpc-lattice (>=1.26.0,<1.27.0)"] waf = ["mypy-boto3-waf (>=1.26.0,<1.27.0)"] waf-regional = ["mypy-boto3-waf-regional (>=1.26.0,<1.27.0)"] wafv2 = ["mypy-boto3-wafv2 (>=1.26.0,<1.27.0)"] @@ -416,7 +412,7 @@ xray = ["mypy-boto3-xray (>=1.26.0,<1.27.0)"] [[package]] name = "botocore" -version = "1.28.5" +version = "1.29.128" description = "Low-level, data-driven core of boto 3." category = "main" optional = false @@ -428,11 +424,11 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = ">=1.25.4,<1.27" [package.extras] -crt = ["awscrt (==0.14.0)"] +crt = ["awscrt (==0.16.9)"] [[package]] name = "botocore-stubs" -version = "1.29.69" +version = "1.29.128" description = "Type annotations and code completion for botocore" category = "main" optional = false @@ -462,14 +458,11 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "2.1.1" +version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=3.6.0" - -[package.extras] -unicode_backport = ["unicodedata2"] +python-versions = ">=3.7.0" [[package]] name = "colorama" @@ -489,7 +482,7 @@ python-versions = ">=3.6" [[package]] name = "coverage" -version = "7.1.0" +version = "7.2.5" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -500,7 +493,7 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "39.0.1" +version = "40.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "dev" optional = false @@ -512,10 +505,10 @@ cffi = ">=1.12" [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] -pep8test = ["black", "ruff", "mypy", "types-pytz", "types-requests", "check-manifest"] +pep8test = ["black", "ruff", "mypy", "check-manifest"] sdist = ["setuptools-rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.2.0)", "pytest-shard (>=0.1.2)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=6.2.0)", "pytest-shard (>=0.1.2)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601"] test-randomorder = ["pytest-randomly"] tox = ["tox"] @@ -530,6 +523,25 @@ python-versions = ">=3.7" [package.extras] graph = ["objgraph (>=1.7.2)"] +[[package]] +name = "docopt" +version = "0.6.2" +description = "Pythonic argument parser, that will make you smile" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "exceptiongroup" +version = "1.1.1" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "flake8" version = "6.0.0" @@ -578,6 +590,14 @@ docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo" perf = ["ipython"] testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = ">=3.7" + [[package]] name = "jinja2" version = "3.1.2" @@ -616,17 +636,9 @@ category = "dev" optional = false python-versions = ">=3.6" -[[package]] -name = "more-itertools" -version = "9.0.0" -description = "More routines for operating on iterables, beyond itertools" -category = "dev" -optional = false -python-versions = ">=3.7" - [[package]] name = "moto" -version = "4.1.2" +version = "4.1.8" description = "" category = "dev" optional = false @@ -644,17 +656,17 @@ werkzeug = ">=0.5,<2.2.0 || >2.2.0,<2.2.1 || >2.2.1" xmltodict = "*" [package.extras] -all = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "ecdsa (!=0.15)", "docker (>=2.5.1)", "graphql-core", "PyYAML (>=5.1)", "cfn-lint (>=0.40.0)", "sshpubkeys (>=3.1.0)", "openapi-spec-validator (>=0.2.8)", "pyparsing (>=3.0.7)", "jsondiff (>=1.1.2)", "aws-xray-sdk (>=0.93,!=0.96)", "setuptools"] +all = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "ecdsa (!=0.15)", "docker (>=3.0.0)", "graphql-core", "PyYAML (>=5.1)", "cfn-lint (>=0.40.0)", "sshpubkeys (>=3.1.0)", "openapi-spec-validator (>=0.2.8)", "pyparsing (>=3.0.7)", "jsondiff (>=1.1.2)", "py-partiql-parser (==0.3.0)", "aws-xray-sdk (>=0.93,!=0.96)", "setuptools"] apigateway = ["PyYAML (>=5.1)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "ecdsa (!=0.15)", "openapi-spec-validator (>=0.2.8)"] apigatewayv2 = ["PyYAML (>=5.1)"] appsync = ["graphql-core"] -awslambda = ["docker (>=2.5.1)"] -batch = ["docker (>=2.5.1)"] -cloudformation = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "ecdsa (!=0.15)", "docker (>=2.5.1)", "graphql-core", "PyYAML (>=5.1)", "cfn-lint (>=0.40.0)", "sshpubkeys (>=3.1.0)", "openapi-spec-validator (>=0.2.8)", "pyparsing (>=3.0.7)", "jsondiff (>=1.1.2)", "aws-xray-sdk (>=0.93,!=0.96)", "setuptools"] +awslambda = ["docker (>=3.0.0)"] +batch = ["docker (>=3.0.0)"] +cloudformation = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "ecdsa (!=0.15)", "docker (>=3.0.0)", "graphql-core", "PyYAML (>=5.1)", "cfn-lint (>=0.40.0)", "sshpubkeys (>=3.1.0)", "openapi-spec-validator (>=0.2.8)", "pyparsing (>=3.0.7)", "jsondiff (>=1.1.2)", "py-partiql-parser (==0.3.0)", "aws-xray-sdk (>=0.93,!=0.96)", "setuptools"] cognitoidp = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "ecdsa (!=0.15)"] ds = ["sshpubkeys (>=3.1.0)"] -dynamodb = ["docker (>=2.5.1)"] -dynamodbstreams = ["docker (>=2.5.1)"] +dynamodb = ["docker (>=3.0.0)", "py-partiql-parser (==0.3.0)"] +dynamodbstreams = ["docker (>=3.0.0)", "py-partiql-parser (==0.3.0)"] ebs = ["sshpubkeys (>=3.1.0)"] ec2 = ["sshpubkeys (>=3.1.0)"] efs = ["sshpubkeys (>=3.1.0)"] @@ -662,8 +674,8 @@ eks = ["sshpubkeys (>=3.1.0)"] glue = ["pyparsing (>=3.0.7)"] iotdata = ["jsondiff (>=1.1.2)"] route53resolver = ["sshpubkeys (>=3.1.0)"] -s3 = ["PyYAML (>=5.1)"] -server = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "ecdsa (!=0.15)", "docker (>=2.5.1)", "graphql-core", "PyYAML (>=5.1)", "cfn-lint (>=0.40.0)", "sshpubkeys (>=3.1.0)", "openapi-spec-validator (>=0.2.8)", "pyparsing (>=3.0.7)", "jsondiff (>=1.1.2)", "aws-xray-sdk (>=0.93,!=0.96)", "setuptools", "flask (!=2.2.0,!=2.2.1)", "flask-cors"] +s3 = ["PyYAML (>=5.1)", "py-partiql-parser (==0.3.0)"] +server = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "ecdsa (!=0.15)", "docker (>=3.0.0)", "graphql-core", "PyYAML (>=5.1)", "cfn-lint (>=0.40.0)", "sshpubkeys (>=3.1.0)", "openapi-spec-validator (>=0.2.8)", "pyparsing (>=3.0.7)", "jsondiff (>=1.1.2)", "py-partiql-parser (==0.3.0)", "aws-xray-sdk (>=0.93,!=0.96)", "setuptools", "flask (!=2.2.0,!=2.2.1)", "flask-cors"] ssm = ["PyYAML (>=5.1)"] xray = ["aws-xray-sdk (>=0.93,!=0.96)", "setuptools"] @@ -680,14 +692,14 @@ dill = ">=0.3.6" [[package]] name = "mypy" -version = "1.0.0" +version = "1.2.0" description = "Optional static typing for Python" category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] -mypy-extensions = ">=0.4.3" +mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = ">=3.10" @@ -718,15 +730,15 @@ python-versions = ">=3.5" [[package]] name = "numpy" -version = "1.23.4" -description = "NumPy is the fundamental package for array computing with Python." +version = "1.24.3" +description = "Fundamental package for array computing in Python" category = "main" optional = false python-versions = ">=3.8" [[package]] name = "packaging" -version = "23.0" +version = "23.1" description = "Core utilities for Python packages" category = "main" optional = false @@ -734,7 +746,7 @@ python-versions = ">=3.7" [[package]] name = "pandas" -version = "1.5.1" +version = "2.0.1" description = "Powerful data structures for data analysis, time series, and statistics" category = "main" optional = false @@ -744,12 +756,34 @@ python-versions = ">=3.8" numpy = [ {version = ">=1.20.3", markers = "python_version < \"3.10\""}, {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, + {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, ] -python-dateutil = ">=2.8.1" +python-dateutil = ">=2.8.2" pytz = ">=2020.1" +tzdata = ">=2022.1" [package.extras] -test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] +all = ["beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "PyQt5 (>=5.15.1)", "pyreadstat (>=1.1.2)", "pytest (>=7.0.0)", "pytest-xdist (>=2.2.0)", "pytest-asyncio (>=0.17.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "scipy (>=1.7.1)", "s3fs (>=2021.08.0)", "SQLAlchemy (>=1.4.16)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"] +aws = ["s3fs (>=2021.08.0)"] +clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"] +compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"] +computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"] +feather = ["pyarrow (>=7.0.0)"] +fss = ["fsspec (>=2021.07.0)"] +gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"] +hdf5 = ["tables (>=3.6.1)"] +html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"] +mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"] +output_formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"] +parquet = ["pyarrow (>=7.0.0)"] +performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"] +plot = ["matplotlib (>=3.6.1)"] +postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"] +spss = ["pyreadstat (>=1.1.2)"] +sql-other = ["SQLAlchemy (>=1.4.16)"] +test = ["hypothesis (>=6.34.2)", "pytest (>=7.0.0)", "pytest-xdist (>=2.2.0)", "pytest-asyncio (>=0.17.0)"] +xml = ["lxml (>=4.6.3)"] [[package]] name = "pathos" @@ -767,14 +801,15 @@ ppft = ">=1.7.6.6" [[package]] name = "pluggy" -version = "0.13.1" +version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.extras] dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] name = "pox" @@ -815,14 +850,6 @@ python-versions = "*" protobuf = ">=2.3.0" six = "*" -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - [[package]] name = "pycodestyle" version = "2.10.0" @@ -841,7 +868,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pydantic" -version = "1.10.4" +version = "1.10.7" description = "Data validation and settings management using python type hints" category = "main" optional = false @@ -864,25 +891,22 @@ python-versions = ">=3.6" [[package]] name = "pytest" -version = "5.4.3" +version = "7.3.1" description = "pytest: simple powerful testing with Python" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=17.4.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} -more-itertools = ">=4.0.0" +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0" -py = ">=1.5.0" -wcwidth = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -checkqa-mypy = ["mypy (==v0.761)"] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "pytest-mock" @@ -898,6 +922,20 @@ pytest = ">=5.0" [package.extras] dev = ["pre-commit", "tox", "pytest-asyncio"] +[[package]] +name = "pytest-watch" +version = "4.2.0" +description = "Local continuous test runner with pytest and watchdog." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +colorama = ">=0.3.3" +docopt = ">=0.4.0" +pytest = ">=2.6.4" +watchdog = ">=0.6.0" + [[package]] name = "python-dateutil" version = "2.8.2" @@ -911,7 +949,7 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2022.5" +version = "2023.3" description = "World timezone definitions, modern and historical" category = "main" optional = false @@ -927,17 +965,17 @@ python-versions = ">=3.6" [[package]] name = "requests" -version = "2.28.1" +version = "2.30.0" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.7" [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" +charset-normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" +urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] @@ -945,24 +983,24 @@ use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "responses" -version = "0.22.0" +version = "0.23.1" description = "A utility library for mocking out the `requests` Python library." category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] +pyyaml = "*" requests = ">=2.22.0,<3.0" -toml = "*" -types-toml = "*" +types-PyYAML = "*" urllib3 = ">=1.25.10" [package.extras] -tests = ["pytest (>=7.0.0)", "coverage (>=6.0.0)", "pytest-cov", "pytest-asyncio", "pytest-httpserver", "flake8", "types-requests", "mypy"] +tests = ["pytest (>=7.0.0)", "coverage (>=6.0.0)", "pytest-cov", "pytest-asyncio", "pytest-httpserver", "flake8", "types-requests", "mypy", "tomli-w", "tomli"] [[package]] name = "s3transfer" -version = "0.6.0" +version = "0.6.1" description = "An Amazon S3 Transfer Manager" category = "main" optional = false @@ -976,7 +1014,7 @@ crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] [[package]] name = "sagemaker" -version = "2.116.0" +version = "2.142.0" description = "Open source library for training and deploying models on Amazon SageMaker." category = "main" optional = false @@ -984,7 +1022,7 @@ python-versions = ">= 3.6" [package.dependencies] attrs = ">=20.3.0,<23" -boto3 = ">=1.20.21,<2.0" +boto3 = ">=1.26.28,<2.0" google-pasta = "*" importlib-metadata = ">=1.4.0,<5.0" numpy = ">=1.9.0,<2.0" @@ -997,10 +1035,10 @@ schema = "*" smdebug_rulesconfig = "1.0.1" [package.extras] -all = ["urllib3 (==1.26.8)", "docker-compose (==1.29.2)", "docker (>=5.0.2,<7.0.0)", "PyYAML (==5.4.1)", "scipy (==1.7.2)"] +all = ["urllib3 (==1.26.8)", "docker-compose (==1.29.2)", "docker (>=5.0.2,<7.0.0)", "PyYAML (==5.4.1)", "scipy (==1.7.3)"] local = ["urllib3 (==1.26.8)", "docker-compose (==1.29.2)", "docker (>=5.0.2,<7.0.0)", "PyYAML (==5.4.1)"] -scipy = ["scipy (==1.7.2)"] -test = ["urllib3 (==1.26.8)", "docker-compose (==1.29.2)", "docker (>=5.0.2,<7.0.0)", "PyYAML (==5.4.1)", "scipy (==1.7.2)", "tox (==3.24.5)", "flake8 (==4.0.1)", "pytest (==6.2.5)", "pytest-cov (==3.0.0)", "pytest-rerunfailures (==10.2)", "pytest-timeout (==2.1.0)", "pytest-xdist (==2.4.0)", "coverage (>=5.2,<6.2)", "mock (==4.0.3)", "contextlib2 (==21.6.0)", "awslogs (==0.14.0)", "black (==22.3.0)", "stopit (==1.1.2)", "apache-airflow (==2.4.1)", "apache-airflow-providers-amazon (==4.0.0)", "attrs (==22.1.0)", "fabric (==2.6.0)", "requests (==2.27.1)", "sagemaker-experiments (==0.1.35)", "Jinja2 (==3.0.3)", "pandas (>=1.3.5,<1.5)"] +scipy = ["scipy (==1.7.3)"] +test = ["urllib3 (==1.26.8)", "docker-compose (==1.29.2)", "docker (>=5.0.2,<7.0.0)", "PyYAML (==5.4.1)", "scipy (==1.7.3)", "tox (==3.24.5)", "flake8 (==4.0.1)", "pytest (==6.2.5)", "pytest-cov (==3.0.0)", "pytest-rerunfailures (==10.2)", "pytest-timeout (==2.1.0)", "pytest-xdist (==2.4.0)", "coverage (>=5.2,<6.2)", "mock (==4.0.3)", "contextlib2 (==21.6.0)", "awslogs (==0.14.0)", "black (==22.3.0)", "stopit (==1.1.2)", "apache-airflow (==2.5.1)", "apache-airflow-providers-amazon (==7.2.1)", "attrs (==22.1.0)", "fabric (==2.6.0)", "requests (==2.27.1)", "sagemaker-experiments (==0.1.35)", "Jinja2 (==3.0.3)", "pandas (>=1.3.5,<1.5)", "scikit-learn (==1.0.2)"] [[package]] name = "schema" @@ -1045,14 +1083,6 @@ sagemaker = ">=2.1.0" [package.extras] test = ["tox (>=3.13.1)", "pytest (>=4.4.1)", "stopit (==1.1.2)", "tensorflow (>=1.3.0)", "mock (>=2.0.0)", "contextlib2 (==0.5.5)", "ipython"] -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - [[package]] name = "tomli" version = "2.0.1" @@ -1063,15 +1093,23 @@ python-versions = ">=3.7" [[package]] name = "types-awscrt" -version = "0.16.10" +version = "0.16.16" description = "Type annotations and code completion for awscrt" category = "main" optional = false python-versions = ">=3.7,<4.0" +[[package]] +name = "types-pyyaml" +version = "6.0.12.9" +description = "Typing stubs for PyYAML" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "types-s3transfer" -version = "0.6.0.post5" +version = "0.6.1" description = "Type annotations and code completion for s3transfer" category = "main" optional = false @@ -1080,29 +1118,29 @@ python-versions = ">=3.7,<4.0" [package.dependencies] types-awscrt = "*" -[[package]] -name = "types-toml" -version = "0.10.8.3" -description = "Typing stubs for toml" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "typing-extensions" -version = "4.4.0" +version = "4.5.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "tzdata" +version = "2023.3" +description = "Provider of IANA time zone data" +category = "main" +optional = false +python-versions = ">=2" + [[package]] name = "urllib3" -version = "1.26.12" +version = "1.26.15" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.extras] brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] @@ -1110,26 +1148,29 @@ secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "cer socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] -name = "wcwidth" -version = "0.2.5" -description = "Measures the displayed width of unicode strings in a terminal" +name = "watchdog" +version = "3.0.0" +description = "Filesystem events monitoring" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "werkzeug" -version = "2.2.3" +version = "2.3.3" description = "The comprehensive WSGI web application library." category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" [package.dependencies] MarkupSafe = ">=2.1.1" [package.extras] -watchdog = ["watchdog"] +watchdog = ["watchdog (>=2.3)"] [[package]] name = "xmltodict" @@ -1141,23 +1182,22 @@ python-versions = ">=3.4" [[package]] name = "zipp" -version = "3.10.0" +version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "jaraco.functools", "more-itertools", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "jaraco.functools", "more-itertools", "big-o", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"] [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "98dab27d51323638b93bba0bce1b32c3822f2f1cfbc9e81d60d23c941770e546" +content-hash = "260cfca732038e2d40150eda9fb29bf580b3bf6c21739ebec7c2e6ebdbe0eed3" [metadata.files] -atomicwrites = [] attrs = [] aws-lambda-powertools = [] boto3 = [] @@ -1172,22 +1212,17 @@ contextlib2 = [] coverage = [] cryptography = [] dill = [] +docopt = [] +exceptiongroup = [] flake8 = [] -google-pasta = [ - {file = "google-pasta-0.2.0.tar.gz", hash = "sha256:c9f2c8dfc8f96d0d5808299920721be30c9eec37f2389f28904f454565c8a16e"}, - {file = "google_pasta-0.2.0-py2-none-any.whl", hash = "sha256:4612951da876b1a10fe3960d7226f0c7682cf901e16ac06e473b267a5afa8954"}, - {file = "google_pasta-0.2.0-py3-none-any.whl", hash = "sha256:b32482794a366b5366a32c92a9a9201b107821889935a02b3e51f6b432ea84ed"}, -] +google-pasta = [] idna = [] importlib-metadata = [] +iniconfig = [] jinja2 = [] jmespath = [] markupsafe = [] -mccabe = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] -more-itertools = [] +mccabe = [] moto = [] multiprocess = [] mypy = [] @@ -1197,102 +1232,37 @@ numpy = [] packaging = [] pandas = [] pathos = [] -pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, -] +pluggy = [] pox = [] ppft = [] protobuf = [] -protobuf3-to-dict = [ - {file = "protobuf3-to-dict-0.1.5.tar.gz", hash = "sha256:1e42c25b5afb5868e3a9b1962811077e492c17557f9c66f0fe40d821375d2b5a"}, -] -py = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] +protobuf3-to-dict = [] pycodestyle = [] -pycparser = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] +pycparser = [] pydantic = [] pyflakes = [] -pytest = [ - {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, - {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, -] +pytest = [] pytest-mock = [] -python-dateutil = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] +pytest-watch = [] +python-dateutil = [] pytz = [] -pyyaml = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, -] +pyyaml = [] requests = [] responses = [] s3transfer = [] sagemaker = [] schema = [] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -smdebug-rulesconfig = [ - {file = "smdebug_rulesconfig-1.0.1-py2.py3-none-any.whl", hash = "sha256:104da3e6931ecf879dfc687ca4bbb3bee5ea2bc27f4478e9dbb3ee3655f1ae61"}, - {file = "smdebug_rulesconfig-1.0.1.tar.gz", hash = "sha256:7a19e6eb2e6bcfefbc07e4a86ef7a88f32495001a038bf28c7d8e77ab793fcd6"}, -] -stepfunctions = [ - {file = "stepfunctions-2.3.0.tar.gz", hash = "sha256:77c668952e2532762e5beb8afe53ab684b9d2964418c4a4b5730c8e40100c3e9"}, -] -toml = [] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] +six = [] +smdebug-rulesconfig = [] +stepfunctions = [] +tomli = [] types-awscrt = [] +types-pyyaml = [] types-s3transfer = [] -types-toml = [] typing-extensions = [] +tzdata = [] urllib3 = [] -wcwidth = [ - {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, - {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, -] +watchdog = [] werkzeug = [] xmltodict = [] zipp = [] diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 9cf54df5..eb1f6681 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -16,13 +16,14 @@ Werkzeug = "^2.2.3" certifi = "2022.12.07" [tool.poetry.dev-dependencies] -pytest = "^5.2" +pytest = "^7" boto3 = "^1.24.45" flake8 = "^6.0.0" coverage = "^7.1.0" mypy = "^1.0.0" pytest-mock = "^3.10.0" moto = "^4.1.2" +pytest-watch = "^4.2.0" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/backend/tests/handlers/__init__.py b/backend/tests/handlers/__init__.py new file mode 100644 index 00000000..082b7a53 --- /dev/null +++ b/backend/tests/handlers/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 diff --git a/backend/tests/handlers/authz/__init__.py b/backend/tests/handlers/authz/__init__.py new file mode 100644 index 00000000..082b7a53 --- /dev/null +++ b/backend/tests/handlers/authz/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 diff --git a/backend/tests/handlers/authz/test_opensearch.py b/backend/tests/handlers/authz/test_opensearch.py new file mode 100644 index 00000000..a5764b2b --- /dev/null +++ b/backend/tests/handlers/authz/test_opensearch.py @@ -0,0 +1,394 @@ +# Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +from backend.handlers.authz.opensearch import AuthEntities +import pytest + + +# a constraint object looks like this +# { +# "entityType": "constraint", +# "constraintId": "0346b172-486a-421c-af57-387f33ce474c", +# "groupPermissions": [ +# { +# "permission": "Read", +# "id": "76892a34-46d6-47d2-8c75-91e38ee4590e", +# "groupId": "vams:all_users" +# }, +# { +# "permission": "Edit", +# "id": "76892a34-46d6-47d2-8c75-91e38ee4590e", +# "groupId": "vams:all_users" +# }, +# { +# "permission": "Admin", +# "id": "f08d8687-4bb1-4548-8768-452c50cf8e5e", +# "groupId": "XXXXXXXXXXXXXXXXX" +# }], +# "description": "The description of this constraint", +# "name": "nameoftheconstraint", +# "criteria": [ +# { +# "id": "ctzl02dw4e", +# "field": "labels", +# "value": "top-secret", +# "operator": "contains" +# }, +# { +# "id": "ctzl02dw4e", +# "field": "labels", +# "value": "private", +# "operator": "does_not_contain" +# }, +# { +# "id": "cjb0luxsq5", +# "field": "f2", +# "value": "restricted1, restricted2", +# "operator": "is_one_of" +# }, +# { +# "id": "cjb0luxsq5", +# "field": "f2", +# "value": "restricted,private,secret", +# "operator": "is_not_one_of" +# } +# ] +# } + +@pytest.fixture +def claims_fixture(): + return [{'entityType': 'constraint', + 'constraintId': '0346b172-486a-421c-af57-387f33ce474c', + 'groupPermissions': [{'permission': 'Admin', + 'id': '76892a34-46d6-47d2-8c75-91e38ee4590e', + 'groupId': 'vams:all_users'}, + {'permission': 'Admin', + 'id': 'f08d8687-4bb1-4548-8768-452c50cf8e5e', + 'groupId': 'arccos@amazon.com'}], + 'description': 'This constraint requires groups. ', + 'name': 'groupsconstraint', + 'criteria': [{'id': 'ctzl02dw4e', + 'field': 'labels', + 'value': 'top-secret', + 'operator': 'contains'}]}, + {'entityType': 'constraint', + 'constraintId': '0a19333c-f50e-48a6-8614-a8ccbfce6878', + 'groupPermissions': [{'permission': 'Edit', + 'id': 'a43c6471-7550-4df7-b2fd-324acf559cc1', + 'groupId': '008fe5a3-6769-4915-a6b2-348ff61241a0'}], + 'description': 'test6', + 'name': 'test6', + 'criteria': [{'id': 'cjb0luxsq5', + 'field': 'f2', + 'value': 'restricted', + 'operator': 'is_one_of'}]}, + {'entityType': 'constraint', + 'constraintId': '25239109-0329-4c99-8fbc-9bbfa7546e1c', + 'groupPermissions': [{'permission': 'Edit', + 'id': '2f591908-6769-42eb-ba21-1cd741579890', + 'groupId': '63030da9-4e79-402b-9d0d-78694467cdd1'}], + 'description': 'test4', + 'name': 'test3', + 'criteria': [{'id': 'cos7jgsq57', + 'field': 'f1', + 'value': 'secret', + 'operator': 'contains'}]}, + {'entityType': 'constraint', + 'constraintId': '8d516da7-b69a-4178-b911-7f2954bae82e', + 'groupPermissions': [{'permission': 'Edit', + 'id': '7e90345e-82ae-472c-958a-aadeed35a56a', + 'groupId': '63030da9-4e79-402b-9d0d-78694467cdd1'}], + 'description': 'this tests a second constraint', + 'name': 'test2', + 'criteria': [{'id': 'cc1kc7z7at', + 'field': 'labels', + 'value': 'not this one', + 'operator': 'is_not_one_of'}]}, + {'entityType': 'constraint', + 'constraintId': 'e82f202b-22ea-4d58-a9b4-875648ab7d59', + 'groupPermissions': [{'permission': 'Read', + 'id': 'e3b4149e-18dd-4f22-b6cc-eb6419a76d7f', + 'groupId': '63030da9-4e79-402b-9d0d-78694467cdd1'}], + 'description': 'test5', + 'name': 'test5', + 'criteria': [{'id': 'cr29eeuku0', + 'field': 'f1', + 'value': 'restricted', + 'operator': 'is_not_one_of'}]}] + + +@pytest.fixture +def claims_fixture_is_one_of_operator(): + return [{'entityType': 'constraint', + 'constraintId': '0346b172-486a-421c-af57-387f33ce474c', + 'groupPermissions': [{'permission': 'Admin', + 'id': '76892a34-46d6-47d2-8c75-91e38ee4590e', + 'groupId': 'vams:all_users'}, + {'permission': 'Admin', + 'id': 'f08d8687-4bb1-4548-8768-452c50cf8e5e', + 'groupId': 'XXXXXXXXXXXXXXXXX'}], + 'description': 'This constraint requires groups. ', + 'name': 'groupsconstraint', + 'criteria': [{'id': 'ctzl02dw4e', + 'field': 'labels', + 'value': 'top-secret', + 'operator': 'is_one_of'}]}] + + +@pytest.fixture +def claims_fixture_contains_operator_single_criteria(): + return [{'entityType': 'constraint', + 'constraintId': '0346b172-486a-421c-af57-387f33ce474c', + 'groupPermissions': [{'permission': 'Admin', + 'id': '76892a34-46d6-47d2-8c75-91e38ee4590e', + 'groupId': 'vams:all_users'}, + {'permission': 'Admin', + 'id': 'f08d8687-4bb1-4548-8768-452c50cf8e5e', + 'groupId': 'XXXXXXXXXXXXXXXXX'}], + 'description': 'This constraint requires groups. ', + 'name': 'groupsconstraint', + 'criteria': [{'id': 'ctzl02dw4e', + 'field': 'labels', + 'value': 'top-secret', + 'operator': 'contains'}]}] + + +def test_contains_operator(claims_fixture_contains_operator_single_criteria): + auth = AuthEntities(None) + result = auth.claims_to_opensearch_filters(claims_fixture_contains_operator_single_criteria, {"vams:all_users"}) + assert result['query']['query_string']['query'] == '(labels:(top-secret))' + + +@pytest.fixture +def claims_fixture_contains_multiple_criteria(): + return [{'entityType': 'constraint', + 'constraintId': '0346b172-486a-421c-af57-387f33ce474c', + 'groupPermissions': [{'permission': 'Admin', + 'id': '76892a34-46d6-47d2-8c75-91e38ee4590e', + 'groupId': 'vams:all_users'}, + {'permission': 'Admin', + 'id': 'f08d8687-4bb1-4548-8768-452c50cf8e5e', + 'groupId': 'XXXXXXXXXXXXXXXXX'}], + 'description': 'This constraint requires groups. ', + 'name': 'groupsconstraint', + 'criteria': [{'id': 'ctzl02dw4e', + 'field': 'labels', + 'value': 'top-secret', + 'operator': 'contains'}, + {'id': 'ctzl02dw4e', + 'field': 'labels', + 'value': 'private', + 'operator': 'contains'}]}] + + +def test_contains_multiple_criteria(claims_fixture_contains_multiple_criteria): + auth = AuthEntities(None) + result = auth.claims_to_opensearch_filters(claims_fixture_contains_multiple_criteria, {"vams:all_users"}) + assert result['query']['query_string']['query'] == '(labels:(top-secret) AND labels:(private))' + + +@pytest.fixture +def claims_fixture_is_one_of_operator_all_users(): + return [{'entityType': 'constraint', + 'constraintId': '0346b172-486a-421c-af57-387f33ce474c', + 'groupPermissions': [{'permission': 'Admin', + 'id': '76892a34-46d6-47d2-8c75-91e38ee4590e', + 'groupId': 'vams:all_users'}, + ], + 'description': 'description of the group', + 'name': 'groupsconstraint', + 'criteria': [{'id': 'ctzl02dw4e', + 'field': 'labels', + 'value': 'top-secret, private, quiet', + 'operator': 'is_one_of'}, + {'id': 'ctzl02dw4e', + 'field': 'secondfield', + 'value': 'top-secret, hidden', + 'operator': 'is_one_of'}, + ]}] + + +def test_is_one_of_operator(claims_fixture_is_one_of_operator_all_users): + auth = AuthEntities(None) + result = auth.claims_to_opensearch_filters(claims_fixture_is_one_of_operator_all_users, {"vams:all_users"}) + assert result['query']['query_string']['query'] == \ + '(labels:("top-secret" OR "private" OR "quiet") AND secondfield:("top-secret" OR "hidden"))' + + +@pytest.fixture +def claims_fixture_is_not_one_of_operator(): + return [{'entityType': 'constraint', + 'constraintId': '0346b172-486a-421c-af57-387f33ce474c', + 'groupPermissions': [{'permission': 'Admin', + 'id': '76892a34-46d6-47d2-8c75-91e38ee4590e', + 'groupId': 'vams:all_users'}, + ], + 'description': 'description of the group', + 'name': 'groupsconstraint', + 'criteria': [{'id': 'ctzl02dw4e', + 'field': 'labels', + 'value': 'top-secret, private, quiet', + 'operator': 'is_not_one_of'}, + {'id': 'ctzl02dw4e', + 'field': 'secondfield', + 'value': 'top-secret, hidden', + 'operator': 'is_not_one_of'}, + ]}] + + +def test_is_not_one_of_operator(claims_fixture_is_not_one_of_operator): + auth = AuthEntities(None) + result = auth.claims_to_opensearch_filters(claims_fixture_is_not_one_of_operator, {"vams:all_users"}) + assert result['query']['query_string']['query'] == \ + '(-labels:("top-secret" OR "private" OR "quiet") AND -secondfield:("top-secret" OR "hidden"))' + + +@pytest.fixture +def claims_fixture_does_not_contain_operator(): + return [{'entityType': 'constraint', + 'constraintId': '0346b172-486a-421c-af57-387f33ce474c', + 'groupPermissions': [{'permission': 'Admin', + 'id': '76892a34-46d6-47d2-8c75-91e38ee4590e', + 'groupId': 'vams:all_users'}, + ], + 'description': 'description of the group', + 'name': 'groupsconstraint', + 'criteria': [{'id': 'ctzl02dw4e', + 'field': 'labels', + 'value': 'top-secret', + 'operator': 'does_not_contain'}, + {'id': 'ctzl02dw4e', + 'field': 'secondfield', + 'value': 'hidden', + 'operator': 'does_not_contain'}, + ]}] + + +def test_does_not_contain_operator(claims_fixture_does_not_contain_operator): + auth = AuthEntities(None) + result = auth.claims_to_opensearch_filters(claims_fixture_does_not_contain_operator, {"vams:all_users"}) + assert result['query']['query_string']['query'] == \ + '(-labels:(top-secret) AND -secondfield:(hidden))' + + +@pytest.fixture +def claims_fixture_2_claims_2_groups(): + return [{'entityType': 'constraint', + 'constraintId': '0346b172-486a-421c-af57-387f33ce474c', + 'groupPermissions': [{'permission': 'Read', + 'id': '76892a34-46d6-47d2-8c75-91e38ee4590e', + 'groupId': 'team3'}, + {'permission': 'Edit', + 'id': 'f08d8687-4bb1-4548-8768-452c50cf8e5e', + 'groupId': 'team1'}], + 'description': 'This constraint requires groups. ', + 'name': 'groupsconstraint', + 'criteria': [ + {'id': 'ctzl02dw4e', + 'field': 'labels', + 'value': 'top-secret', + 'operator': 'contains'}, + {'id': 'ctzl02dw4e', + 'field': 'level', + 'value': 'top-secret', + 'operator': 'contains'}, + + ]}, + {'entityType': 'constraint', + 'constraintId': '0346b172-486a-421c-af57-387f33ce474c', + 'groupPermissions': [{'permission': 'Read', + 'id': '76892a34-46d6-47d2-8c75-91e38ee4590e', + 'groupId': 'team2'}, + {'permission': 'Edit', + 'id': 'f08d8687-4bb1-4548-8768-452c50cf8e5e', + 'groupId': 'team1'}, + {'permission': 'Admin', + 'id': 'f08d8687-4bb1-4548-8768-452c50cf8e5e', + 'groupId': 'team3'} + + ], + 'description': 'This constraint requires groups. ', + 'name': 'groupsconstraint', + 'criteria': [ + {'id': 'ctzl02dw4e', + 'field': 'f2', + 'value': 'private', + 'operator': 'contains'}, + {'id': 'ctzl02dw4e', + 'field': 'f3', + 'value': 'secret', + 'operator': 'contains'}, + + ]}] + + +def test_2_claims_2_groups(claims_fixture_2_claims_2_groups): + auth = AuthEntities(None) + result = auth.claims_to_opensearch_filters(claims_fixture_2_claims_2_groups, {"team1"}) + assert result['query']['query_string']['query'] == \ + '(labels:(top-secret) AND level:(top-secret)) OR (f2:(private) AND f3:(secret))' + + +def test_by_permission(claims_fixture_2_claims_2_groups): + auth = AuthEntities(None) + result = auth.claims_to_opensearch_agg(claims_fixture_2_claims_2_groups, {"team1"}) + assert result is not None + + # The result should include a filters aggregation with 3 buckets (read, edit,admin) using query_string. + # When the particular group does not have a permission, it should be omitted + # + # example = { + # "aggs": { + # "permissions": { + # "filters": { + # "filters": { + # "Read": { + # "query_string": { + # "query": "(labels:(top-secret) AND level:(top-secret))" + # } + # }, + # "Edit": { + # "query_string": { + # "query": "(f2:(private) AND f3:(secret))" + # } + # } + # } + # } + # } + # } + # } + + # team1 only has Edit access to both claims, so the Read and Admin buckets should be omitted + assert result['aggs']['permissions'] is not None + assert result['aggs']['permissions']['filters']['filters']['Edit']['query_string']['query'] == \ + "(labels:(top-secret) AND level:(top-secret)) OR (f2:(private) AND f3:(secret))" + + assert "Read" not in result['aggs']['permissions']['filters']['filters'] + assert "Admin" not in result['aggs']['permissions']['filters']['filters'] + assert "Edit" in result['aggs']['permissions']['filters']['filters'] + + # team3 has Admin and Edit access in two claims + result = auth.claims_to_opensearch_agg(claims_fixture_2_claims_2_groups, {"team3"}) + assert "Read" in result['aggs']['permissions']['filters']['filters'] + assert "Edit" not in result['aggs']['permissions']['filters']['filters'] + assert "Admin" in result['aggs']['permissions']['filters']['filters'] + assert result['aggs']['permissions']['filters']['filters']['Read']['query_string']['query'] == \ + "(labels:(top-secret) AND level:(top-secret))" + assert result['aggs']['permissions']['filters']['filters']['Admin']['query_string']['query'] == \ + "(f2:(private) AND f3:(secret))" + + # what if someone is a member of all 3 teams? + result = auth.claims_to_opensearch_agg(claims_fixture_2_claims_2_groups, {"team1", "team2", "team3"}) + assert "Read" in result['aggs']['permissions']['filters']['filters'] + assert "Edit" in result['aggs']['permissions']['filters']['filters'] + assert "Admin" in result['aggs']['permissions']['filters']['filters'] + assert result['aggs']['permissions']['filters']['filters']['Read']['query_string']['query'] == \ + "(labels:(top-secret) AND level:(top-secret)) OR (f2:(private) AND f3:(secret))" + assert result['aggs']['permissions']['filters']['filters']['Edit']['query_string']['query'] == \ + "(labels:(top-secret) AND level:(top-secret)) OR (f2:(private) AND f3:(secret))" + assert result['aggs']['permissions']['filters']['filters']['Admin']['query_string']['query'] == \ + "(f2:(private) AND f3:(secret))" + + result = auth.claims_to_opensearch_agg(claims_fixture_2_claims_2_groups, {"team3"}) + assert result['aggs']['permissions']['filters']['filters']['Read']['query_string']['query'] == \ + "(labels:(top-secret) AND level:(top-secret))" diff --git a/infra/lib/api-builder.ts b/infra/lib/api-builder.ts index 6f9d5354..1f5717f3 100644 --- a/infra/lib/api-builder.ts +++ b/infra/lib/api-builder.ts @@ -363,6 +363,19 @@ export function apiBuilder( api: api.apiGatewayV2, }); + attachFunctionToApi(scope, authFunctions.constraints, { + routePath: "/auth/constraints", + method: apigwv2.HttpMethod.GET, + api: api.apiGatewayV2, + }); + for (let i = 0; i < methods.length; i++) { + attachFunctionToApi(scope, authFunctions.constraints, { + routePath: "/auth/constraints/{constraintId}", + method: methods[i], + api: api.apiGatewayV2, + }); + } + //Enabling API Gateway Access Logging: Currently the only way to do this is via V1 constructs //https://github.com/aws/aws-cdk/issues/11100#issuecomment-904627081 const accessLogs = new logs.LogGroup(scope, "VAMS-API-AccessLogs"); diff --git a/infra/lib/infra-stack.ts b/infra/lib/infra-stack.ts index a07b8efc..33479277 100644 --- a/infra/lib/infra-stack.ts +++ b/infra/lib/infra-stack.ts @@ -194,6 +194,8 @@ export class VAMS extends cdk.Stack { }); } + cdk.Tags.of(this).add("vams:stackname", props.stackName); + this.node.findAll().forEach((item) => { if (item instanceof cdk.aws_lambda.Function) { const fn = item as cdk.aws_lambda.Function; diff --git a/infra/lib/lambdaBuilder/authFunctions.ts b/infra/lib/lambdaBuilder/authFunctions.ts index 4825396f..0044ed77 100644 --- a/infra/lib/lambdaBuilder/authFunctions.ts +++ b/infra/lib/lambdaBuilder/authFunctions.ts @@ -11,6 +11,7 @@ import { storageResources } from "../storage-builder"; interface AuthFunctions { groups: lambda.Function; + constraints: lambda.Function; } export function buildAuthFunctions( scope: Construct, @@ -18,6 +19,7 @@ export function buildAuthFunctions( ): AuthFunctions { return { groups: buildAuthFunction(scope, storageResources, "groups"), + constraints: buildAuthFunction(scope, storageResources, "finegrainedaccessconstraints"), }; } diff --git a/infra/lib/lambdaBuilder/metadataFunctions.ts b/infra/lib/lambdaBuilder/metadataFunctions.ts index ffc660bd..441d98e6 100644 --- a/infra/lib/lambdaBuilder/metadataFunctions.ts +++ b/infra/lib/lambdaBuilder/metadataFunctions.ts @@ -5,7 +5,6 @@ import * as lambda from "aws-cdk-lib/aws-lambda"; import * as path from "path"; -import * as dynamodb from "aws-cdk-lib/aws-dynamodb"; import { Construct } from "constructs"; import { Duration } from "aws-cdk-lib"; import { storageResources } from "../storage-builder"; diff --git a/web/package.json b/web/package.json index 7c24e3a4..6cc8a67d 100644 --- a/web/package.json +++ b/web/package.json @@ -46,7 +46,7 @@ "scripts": { "start": "react-scripts start", "build": "react-scripts build", - "test": "react-scripts test", + "test": "react-scripts test --coverage", "eject": "react-scripts eject", "postinstall": "patch-package && del-cli ./node_modules/online-3d-viewer/libs/loaders/occt-import-js-worker.js && del-cli ./node_modules/online-3d-viewer/libs/loaders/occt-import-js.js && del-cli ./node_modules/online-3d-viewer/libs/loaders/occt-import-js.license.md && del-cli ./node_modules/online-3d-viewer/libs/loaders/occt-import-js.wasm" }, @@ -69,6 +69,8 @@ ] }, "devDependencies": { + "@cloudscape-design/jest-preset": "^2.0.8", + "@jest/types": "^29.5.0", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^11.2.7", "@testing-library/react-hooks": "^8.0.1", @@ -77,9 +79,38 @@ "@types/react": "^17.0.2", "@types/react-dom": "^17.0.2", "assert": "^2.0.0", + "babel-jest": "^29.5.0", "prettier": "^2.4.1", "react-error-overlay": "^6.0.9", + "react-test-renderer": "^17.0.2", "sass": "^1.43.4", - "sass-loader": "^12.3.0" + "sass-loader": "^12.3.0", + "ts-node": "^10.9.1" + }, + "jest": { + "collectCoverageFrom": [ + "src/**/*.{js,jsx,ts,tsx}", + "!/node_modules/", + "!/path/to/dir/" + ], + "transform": { + "node_modules/@cloudscape-design/.+\\.js$": "./node_modules/@cloudscape-design/jest-preset/js-transformer", + "node_modules/@cloudscape-design/.+\\.css": "./node_modules/@cloudscape-design/jest-preset/css-transformer", + "node_modules/(d3-.*|internmap)/.+\\.js$": "./node_modules/@cloudscape-design/jest-preset/js-transformer" + }, + "transformIgnorePatterns": [ + "/node_modules/(?!(d3-.*|internmap|@cloudscape-design/)).+\\.js$" + ], + "coverageThreshold": { + "global": { + "branches": 6, + "functions": 11, + "lines": 11, + "statements": 10 + } + }, + "coverageReporters": [ + "text" + ] } } diff --git a/web/src/FedAuth/VAMSAuth.tsx b/web/src/FedAuth/VAMSAuth.tsx index 56e97923..b755ae69 100644 --- a/web/src/FedAuth/VAMSAuth.tsx +++ b/web/src/FedAuth/VAMSAuth.tsx @@ -153,7 +153,11 @@ const FedLoginBox: React.FC = ({ onLogin, onLocal }) => { alt="Visual Asset Management System Logo" /> -

@@ -167,6 +171,7 @@ const FedLoginBox: React.FC = ({ onLogin, onLocal }) => { }} href="#local" className={styles.link} + data-testid="local-creds-link" > Login with local credentials diff --git a/web/src/components/createupdate/CreateDatabase.tsx b/web/src/components/createupdate/CreateDatabase.tsx index d9c5eaac..02d3cf2a 100644 --- a/web/src/components/createupdate/CreateDatabase.tsx +++ b/web/src/components/createupdate/CreateDatabase.tsx @@ -148,6 +148,7 @@ export default function CreateDatabase({ null ) } + data-testid={`${createOrUpdate}-database-button`} > {createOrUpdate} Database @@ -175,6 +176,7 @@ export default function CreateDatabase({ setFormState({ ...formState, databaseId: detail.value }) } placeholder="Database Name" + data-testid="database-name" /> diff --git a/web/src/components/createupdate/form-definitions/PipelineFormDefinition.js b/web/src/components/createupdate/form-definitions/PipelineFormDefinition.js index bc7549de..7f572bae 100644 --- a/web/src/components/createupdate/form-definitions/PipelineFormDefinition.js +++ b/web/src/components/createupdate/form-definitions/PipelineFormDefinition.js @@ -76,6 +76,7 @@ export const PipelineFormDefinition = new FormDefinition({ formElement: Select, elementProps: { disabled: false, + "data-testid": "pipelineType", }, }), required: true, @@ -110,7 +111,7 @@ export const PipelineFormDefinition = new FormDefinition({ constraintText: "Required. Max 256 characters.", elementDefinition: new ElementDefinition({ formElement: Textarea, - elementProps: { rows: 4 }, + elementProps: { rows: 4, "data-testid": "description-textarea" }, }), required: true, }), @@ -121,7 +122,9 @@ export const PipelineFormDefinition = new FormDefinition({ options: fileTypeOptions, elementDefinition: new ElementDefinition({ formElement: Select, - elementProps: {}, + elementProps: { + "data-testid": "inputFileType-select", + }, }), required: true, }), @@ -132,7 +135,9 @@ export const PipelineFormDefinition = new FormDefinition({ options: fileTypeOptions, elementDefinition: new ElementDefinition({ formElement: Select, - elementProps: {}, + elementProps: { + "data-testid": "outputFileType-select", + }, }), required: true, }), diff --git a/web/src/components/interactive/WorkflowEditor.js b/web/src/components/interactive/WorkflowEditor.js index ba47f8cd..779cb579 100644 --- a/web/src/components/interactive/WorkflowEditor.js +++ b/web/src/components/interactive/WorkflowEditor.js @@ -102,7 +102,11 @@ const WorkflowEditor = (props) => { position: { x: xPos.current, y: yPos.current }, data: { label: ( - + ), }, sourcePosition: "bottom", diff --git a/web/src/components/list/TableList.js b/web/src/components/list/TableList.js index 51d94945..4b992937 100644 --- a/web/src/components/list/TableList.js +++ b/web/src/components/list/TableList.js @@ -19,7 +19,6 @@ import { } from "@cloudscape-design/components"; import { EmptyState } from "../../common/common-components"; import ListDefinition from "./list-definitions/types/ListDefinition"; -import { deleteElement } from "../../services/APIService"; export default function TableList(props) { //props @@ -39,8 +38,7 @@ export default function TableList(props) { filterColumns, pluralName, pluralNameTitleCase, - elementId, - deleteRoute, + deleteFunction, } = listDefinition; const filteredVisibleColumns = visibleColumns.filter((columnName) => { if (!databaseId) return true; @@ -150,12 +148,7 @@ export default function TableList(props) { const handleDeleteElements = async (selected) => { setDeleting(true); for (let i = 0; i < selected.length; i++) { - const item = selected[i]; - const result = await deleteElement({ - deleteRoute: deleteRoute, - elementId: elementId, - item: item, - }); + const result = await deleteFunction(selected[i]); if (result !== false && Array.isArray(result)) { if (result[0] === false) { setDeleteResult({ @@ -350,4 +343,6 @@ TableList.propTypes = { listDefinition: PropTypes.instanceOf(ListDefinition).isRequired, databaseId: PropTypes.string, editEnabled: PropTypes.bool, + UpdateSelectedElement: PropTypes.func, + createNewElement: PropTypes.element, }; diff --git a/web/src/components/list/list-definitions/types/ListDefinition.js b/web/src/components/list/list-definitions/types/ListDefinition.js index 83890929..074b2744 100644 --- a/web/src/components/list/list-definitions/types/ListDefinition.js +++ b/web/src/components/list/list-definitions/types/ListDefinition.js @@ -6,6 +6,7 @@ import PropTypes from "prop-types"; import ColumnDefinition from "./ColumnDefinition"; import FilterDefinition from "./FilterDefinition"; +import { deleteElement } from "../../../../services/APIService"; export default function ListDefinition(props) { const { @@ -25,6 +26,17 @@ export default function ListDefinition(props) { this.pluralNameTitleCase = pluralNameTitleCase; this.elementId = elementId; this.deleteRoute = deleteRoute; + if (props.deleteFunction !== null && props.deleteFunction !== undefined) { + this.deleteFunction = props.deleteFunction; + } else { + this.deleteFunction = async function (item) { + return deleteElement({ + deleteRoute: deleteRoute, + elementId: elementId, + item: item, + }); + }; + } } ListDefinition.propTypes = { @@ -35,4 +47,5 @@ ListDefinition.propTypes = { pluralNameTitleCase: PropTypes.string.isRequired, elementId: PropTypes.string.isRequired, deleteRoute: PropTypes.string.isRequired, + deleteFunction: PropTypes.func, }; diff --git a/web/src/components/selectors/DatabaseSelector.js b/web/src/components/selectors/DatabaseSelector.js index d0d906f6..4534af2e 100644 --- a/web/src/components/selectors/DatabaseSelector.js +++ b/web/src/components/selectors/DatabaseSelector.js @@ -35,6 +35,7 @@ const DatabaseSelector = (props) => { })} filteringType="auto" selectedAriaLabel="Selected" + data-testid="database-select" /> ); }; diff --git a/web/src/components/selectors/WorkflowPipelineSelector.js b/web/src/components/selectors/WorkflowPipelineSelector.js index fafd433c..ce3ab9de 100644 --- a/web/src/components/selectors/WorkflowPipelineSelector.js +++ b/web/src/components/selectors/WorkflowPipelineSelector.js @@ -73,6 +73,7 @@ const WorkflowPipelineSelector = (props) => { })} filteringType="auto" selectedAriaLabel="Selected" + data-testid={props["data-testid"] || "wfpipelinesel"} /> ); }; diff --git a/web/src/external/RelatedTableComponent/src/Hooks/useTreeCollection.test.ts b/web/src/external/RelatedTableComponent/src/Hooks/useTreeCollection.test.ts index 990bc55f..593342e9 100644 --- a/web/src/external/RelatedTableComponent/src/Hooks/useTreeCollection.test.ts +++ b/web/src/external/RelatedTableComponent/src/Hooks/useTreeCollection.test.ts @@ -63,11 +63,13 @@ describe("useTreeCollection", () => { }) ); + expect(result.current.items[0].isExpanded()).toEqual(true); + act(() => { result.current.expandNode(result.current.items[0]); }); - expect(result.current.items[0].isExpanded()).toEqual(true); + expect(result.current.items[0].isExpanded()).toEqual(false); }); it("reset nodes", () => { const { result } = renderHook(() => @@ -82,6 +84,8 @@ describe("useTreeCollection", () => { }) ); + expect(result.current.items[0].isExpanded()).toEqual(true); + act(() => { result.current.expandNode(result.current.items[0]); }); @@ -90,6 +94,6 @@ describe("useTreeCollection", () => { result.current.reset(); }); - expect(result.current.items[0].isExpanded()).toEqual(false); + expect(result.current.items[0].isExpanded()).toEqual(true); }); }); diff --git a/web/src/external/RelatedTableComponent/src/RelatedTable/Common/StyledComponents.test.tsx b/web/src/external/RelatedTableComponent/src/RelatedTable/Common/StyledComponents.test.tsx index 29e01da4..4b663979 100644 --- a/web/src/external/RelatedTableComponent/src/RelatedTable/Common/StyledComponents.test.tsx +++ b/web/src/external/RelatedTableComponent/src/RelatedTable/Common/StyledComponents.test.tsx @@ -12,12 +12,12 @@ describe("StyledComponents", () => { it("renders correctly", () => { const { container } = render(); expect(container).toMatchInlineSnapshot(` -

-
-
-`); +
+
+
+ `); }); }); @@ -25,13 +25,13 @@ describe("StyledComponents", () => { it("renders correctly", () => { const { container } = render(); expect(container).toMatchInlineSnapshot(` -
-
-
-`); +
+
+
+ `); }); }); }); diff --git a/web/src/layout/Navigation.js b/web/src/layout/Navigation.js index 8b1dafe6..85a18ec5 100644 --- a/web/src/layout/Navigation.js +++ b/web/src/layout/Navigation.js @@ -48,6 +48,16 @@ let navItems = [ text: "Orchestrate & Automate", items: [{ type: "link", text: "Workflows", href: "/workflows" }], }, + { + type: "divider", + role: "super-admin", + }, + { + type: "section", + role: "super-admin", + text: "Admin", + items: [{ type: "link", text: "Fine Grained Access Controls", href: "/auth/constraints" }], + }, { type: "divider", }, diff --git a/web/src/layout/Navigation.test.tsx b/web/src/layout/Navigation.test.tsx new file mode 100644 index 00000000..dc15562f --- /dev/null +++ b/web/src/layout/Navigation.test.tsx @@ -0,0 +1,123 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { render } from "@testing-library/react"; + +import createWrapper, { ElementWrapper } from "@cloudscape-design/components/test-utils/dom"; + +import { Navigation } from "./Navigation"; + +function createUser(roles: string[]) { + return { + signInUserSession: { + idToken: { + payload: { + "vams:roles": JSON.stringify(roles), + }, + }, + }, + }; +} +function expectSideNavLinks(wrapper: ElementWrapper, ...links: string[]) { + links.forEach((link) => { + expect(wrapper.findSideNavigation()?.findLinkByHref(link)).toBeTruthy(); + }); +} +function expectNoSideNavLinks(wrapper: ElementWrapper, ...links: string[]) { + links.forEach((link) => { + expect(wrapper.findSideNavigation()?.findLinkByHref(link)).toBeFalsy(); + }); +} + +describe("Navigation", () => { + it("should render all links for super-admin", async () => { + const { container } = render( + + ); + const wrapper = createWrapper(container); + expectSideNavLinks( + wrapper, + "/assets", + "/upload", + "/pipelines", + "/workflows", + "/auth/constraints", + "/visualizers/column", + "/visualizers/model", + "/visualizers/plot" + ); + }); + + it("should render assets and visualizer links for the assets role", async () => { + const { container } = render( + + ); + const wrapper = createWrapper(container); + expectSideNavLinks( + wrapper, + "/assets", + "/upload", + "/visualizers/column", + "/visualizers/model", + "/visualizers/plot" + ); + expectNoSideNavLinks(wrapper, "/pipelines", "/workflows", "/auth/constraints"); + }); + + it("should render pipelines for the pipeline role", async () => { + const { container } = render( + + ); + const wrapper = createWrapper(container); + expectSideNavLinks(wrapper, "/pipelines"); + expectNoSideNavLinks( + wrapper, + "/assets", + "/upload", + "/workflows", + "/auth/constraints", + "/visualizers/model", + "/visualizers/plot", + "visualizers/column" + ); + }); + + it("should render workflows for the workflow role", async () => { + const { container } = render( + + ); + const wrapper = createWrapper(container); + expectSideNavLinks(wrapper, "/workflows"); + expectNoSideNavLinks( + wrapper, + "/assets", + "/pipelines", + "/auth/constraints", + "/visualizers/column", + "/visualizers/model", + "/visualizers/plot" + ); + }); + + it("should render combined links for assets, piplines, and workflows roles", async () => { + const { container } = render( + + ); + const wrapper = createWrapper(container); + expectSideNavLinks( + wrapper, + "/assets", + "/pipelines", + "/workflows", + "/visualizers/column", + "/visualizers/model", + "/visualizers/plot" + ); + expectNoSideNavLinks(wrapper, "/auth/constraints"); + }); +}); diff --git a/web/src/pages/AssetUpload.tsx b/web/src/pages/AssetUpload.tsx index ed25450c..cf23588f 100644 --- a/web/src/pages/AssetUpload.tsx +++ b/web/src/pages/AssetUpload.tsx @@ -192,6 +192,7 @@ const UploadForm = () => { > { setAssetDetail((assetDetail) => ({ ...assetDetail, @@ -224,6 +225,7 @@ const UploadForm = () => { }} filteringType="auto" selectedAriaLabel="Selected" + data-testid="isDistributable-select" /> @@ -242,6 +244,7 @@ const UploadForm = () => { })); }} selectedOption={databaseId} + data-testid="database-selector" /> @@ -260,6 +263,7 @@ const UploadForm = () => { description: e.detail.value, })); }} + data-testid="asset-description-textarea" /> @@ -278,6 +282,7 @@ const UploadForm = () => { Comment: e.detail.value, })); }} + data-testid="asset-comment-input" /> @@ -320,6 +325,7 @@ const UploadForm = () => { }} fileFormats={previewFileFormatsStr} file={assetDetail.Preview} + data-testid="preview-file" /> @@ -342,6 +348,7 @@ const UploadForm = () => { resolve(null); }); }} + data-testid="metadata-table" /> @@ -380,6 +387,7 @@ const UploadForm = () => { } selected`; }, }} + data-testid="workflow-table" /> diff --git a/web/src/pages/ListPage.js b/web/src/pages/ListPage.js index 30fe90f0..7b504846 100644 --- a/web/src/pages/ListPage.js +++ b/web/src/pages/ListPage.js @@ -146,6 +146,7 @@ ListPage.propTypes = { listDefinition: PropTypes.instanceOf(ListDefinition).isRequired, CreateNewElement: PropTypes.func, fetchElements: PropTypes.func.isRequired, + fetchAllElements: PropTypes.func, onCreateCallback: PropTypes.func, isRelatedTable: PropTypes.bool, editEnabled: PropTypes.bool, diff --git a/web/src/pages/ListPageNoDatabase.test.tsx b/web/src/pages/ListPageNoDatabase.test.tsx new file mode 100644 index 00000000..64a3cd87 --- /dev/null +++ b/web/src/pages/ListPageNoDatabase.test.tsx @@ -0,0 +1,96 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { render } from "@testing-library/react"; +import { act } from "react-dom/test-utils"; + +import ListPageNoDatabase from "./ListPageNoDatabase"; +import ListDefinition from "../components/list/list-definitions/types/ListDefinition"; +import ColumnDefinition from "../components/list/list-definitions/types/ColumnDefinition"; + +import createWrapper from "@cloudscape-design/components/test-utils/dom"; + +const listDef = new ListDefinition({ + pluralName: "constraints", + pluralNameTitleCase: "Constraints", + visibleColumns: ["name", "description"], + filterColumns: [{ name: "name", placeholder: "Name" }], + elementId: "name", + deleteRoute: "auth/constraints/{constraintId}", + columnDefinitions: [ + new ColumnDefinition({ + id: "name", + header: "Name", + cellWrapper: (props: any) => { + return <>{props.children}; + }, + sortingField: "name", + }), + new ColumnDefinition({ + id: "description", + header: "Description", + cellWrapper: (props: any) => <>{props.children}, + sortingField: "description", + }), + ], +}); + +describe("ListPageNoDatabase", () => { + it("renders without crashing", async () => { + const promise = Promise.resolve([ + { + name: "test", + description: "test", + }, + ]); + + const fetchAllElements = jest.fn(() => promise); + + const { container } = render( + + ); + + await act(async () => { + await promise; + }); + + const wrapper = createWrapper(container!); + expect(container).toBeTruthy(); + expect(fetchAllElements).toHaveBeenCalledTimes(1); + + expect(wrapper.findTable()).toBeTruthy(); + expect(wrapper.findTable()?.findRows()).toHaveLength(1); + + expect(wrapper.findButton("[data-testid=create-new-element-button]")).toBeTruthy(); + }); + + // test('renders the correct text', () => { + // const { getByText } = render(); + // expect(getByText('No database selected')).toBeInTheDocument(); + // }); + + // test('renders the correct text when a database is selected', () => { + // const { getByText } = render(); + // expect(getByText('No tables in database test')).toBeInTheDocument(); + // }); + + // test('renders the correct text when a table is selected', () => { + // const { getByText } = render(); + // expect(getByText('No columns in table test')).toBeInTheDocument(); + // }); + + // test('renders the correct text when a column is selected', () => { + // const { getByText } = render(); + // } +}); diff --git a/web/src/pages/ListPageNoDatabase.tsx b/web/src/pages/ListPageNoDatabase.tsx new file mode 100644 index 00000000..d6252314 --- /dev/null +++ b/web/src/pages/ListPageNoDatabase.tsx @@ -0,0 +1,123 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { useEffect, useState } from "react"; +import Box from "@cloudscape-design/components/box"; +import Button from "@cloudscape-design/components/button"; +import Grid from "@cloudscape-design/components/grid"; +import SpaceBetween from "@cloudscape-design/components/space-between"; +import TextContent from "@cloudscape-design/components/text-content"; +import TableList from "../components/list/TableList"; +import PropTypes from "prop-types"; +import ListDefinition from "../components/list/list-definitions/types/ListDefinition"; +import RelatedTableList from "../components/list/RelatedTableList"; + +export default function ListPageNoDatabase(props: any) { + const { + singularNameTitleCase, + pluralNameTitleCase, + listDefinition, + CreateNewElement, + fetchElements, + fetchAllElements, + onCreateCallback, + isRelatedTable, + editEnabled, + } = props; + const [reload, setReload] = useState(true); + const [loading, setLoading] = useState(true); + const [allItems, setAllItems] = useState>([]); + + const [openNewElement, setOpenNewElement] = useState(false); + + useEffect(() => { + const getData = async () => { + setLoading(true); + let items = await fetchAllElements(); + + if (items !== false && Array.isArray(items)) { + setLoading(false); + setReload(false); + setAllItems(items); + } + }; + if (reload) { + getData(); + } + }, [reload, fetchAllElements, fetchElements]); + + const handleOpenNewElement = () => { + if (onCreateCallback) onCreateCallback(); + else if (CreateNewElement) setOpenNewElement(true); + }; + + return ( + <> + + +
+ +

{pluralNameTitleCase}

+
+
+
+ + {isRelatedTable && ( + + )} + + + + +
+ ) + } + /> + + + {CreateNewElement && ( + + )} + + ); +} + +ListPageNoDatabase.propTypes = { + singularName: PropTypes.string.isRequired, + singularNameTitleCase: PropTypes.string.isRequired, + pluralName: PropTypes.string.isRequired, + pluralNameTitleCase: PropTypes.string.isRequired, + listDefinition: PropTypes.instanceOf(ListDefinition).isRequired, + CreateNewElement: PropTypes.func, + fetchElements: PropTypes.func.isRequired, + fetchAllElements: PropTypes.func, + onCreateCallback: PropTypes.func, + isRelatedTable: PropTypes.bool, + editEnabled: PropTypes.bool, +}; diff --git a/web/src/pages/auth/Constraints.tsx b/web/src/pages/auth/Constraints.tsx new file mode 100644 index 00000000..63c8038f --- /dev/null +++ b/web/src/pages/auth/Constraints.tsx @@ -0,0 +1,74 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// import { fetchConstraints } from "../../services/APIService"; +import CreateConstraint from "./CreateConstraint"; +import ListDefinition from "../../components/list/list-definitions/types/ListDefinition"; +import ColumnDefinition from "../../components/list/list-definitions/types/ColumnDefinition"; +import { API } from "aws-amplify"; +import ListPageNoDatabase from "../ListPageNoDatabase"; + +async function fetchConstraints(api = API) { + try { + const response = await api.get("api", "auth/constraints", {}); + if (response.constraints) { + return response.constraints; + } else { + return false; + } + } catch (error: any) { + console.log(error); + return error?.message; + } +} + +export const ConstraintsListDefinition = new ListDefinition({ + pluralName: "constraints", + pluralNameTitleCase: "Constraints", + visibleColumns: ["name", "description"], + filterColumns: [{ name: "name", placeholder: "Name" }], + elementId: "name", + deleteFunction: (item: any): [boolean, string] => { + try { + const response: any = API.del("api", `auth/constraints/${item.constraintId}`, {}); + return [true, response.message]; + } catch (error: any) { + console.log(error); + return [false, error?.message]; + } + }, + columnDefinitions: [ + new ColumnDefinition({ + id: "name", + header: "Name", + cellWrapper: (props: any) => { + return <>{props.children}; + }, + sortingField: "name", + }), + new ColumnDefinition({ + id: "description", + header: "Description", + cellWrapper: (props: any) => <>{props.children}, + sortingField: "description", + }), + ], +}); + +export default function Constraints() { + return ( + + ); +} diff --git a/web/src/pages/auth/CreateConstraint.tsx b/web/src/pages/auth/CreateConstraint.tsx new file mode 100644 index 00000000..f64e389b --- /dev/null +++ b/web/src/pages/auth/CreateConstraint.tsx @@ -0,0 +1,483 @@ +import { + Box, + Button, + Form, + FormField, + Header, + Input, + Modal, + Select, + SpaceBetween, + Table, + Textarea, +} from "@cloudscape-design/components"; +import { Optional } from "@cloudscape-design/components/internal/types"; +import { API } from "aws-amplify"; +import { useState } from "react"; +import { generateUUID } from "../../common/utils/utils"; +import GroupPermissionsTable, { GroupPermission } from "./GroupPermissionsTable"; + +interface ConstraintCriteria { + id: string; + field: string; + operator: string; + value: string; +} + +interface ConstraintFields { + constraintId: string; + name: string; + description: string; + appliesTo: string[]; + groupPermissions: GroupPermission[]; + criteria: ConstraintCriteria[]; +} + +interface CreateConsraintProps { + open: boolean; + setOpen: (open: boolean) => void; + setReload: (reload: boolean) => void; + initState: any; +} + +// when a string is all lower case, return null, otherwise return the string "All lower case letters only" +function validateNameLowercase(name: string) { + if (name === undefined) return null; + return name.match(/^[a-z0-9_-]+$/) !== null + ? null + : "All lower case letters only. No special characters except - and _"; +} + +// when a string is between 4 and 64 characters, return null, otherwise return the string "Between 4 and 64 characters" +function validateNameLength(name: string) { + if (name === undefined) return null; + return name.length >= 4 && name.length <= 64 ? null : "Between 4 and 64 characters"; +} + +// chain together the above three functions, when they return null, then return null +function validateName(name: string) { + if (name === undefined) return null; + return validateNameLowercase(name) || validateNameLength(name); +} + +// when a string is between the given min and max characters, return null, otherwise return an error message including the range +function validateDescriptionLength(description: string) { + if (description === undefined) return null; + const min = 4, + max = 256; + return description.length >= min && description.length <= max + ? null + : `Between ${min} and ${max} characters`; +} + +export default function CreateConstraint({ + open, + setOpen, + setReload, + initState, +}: CreateConsraintProps) { + const [inProgress, setInProgress] = useState(false); + + const createOrUpdate = (initState && "Update") || "Create"; + const [formState, setFormState] = useState({ + constraintId: generateUUID(), + ...initState, + }); + + const [selectedCriteria, setSelectedCriteria] = useState([]); + + console.log("formState", formState, initState); + + const criteriaOperators = [ + { label: "Contains", value: "contains" }, + { label: "Does Not Contain", value: "does_not_contain" }, + { label: "Is One Of", value: "is_one_of" }, + { label: "Is Not One Of", value: "is_not_one_of" }, + ]; + + function addNewConstraint() { + let criteria = formState.criteria; + if (!criteria) { + criteria = []; + } + criteria.push({ + // create a unique id using a uuid + id: "c" + Math.random().toString(36).substr(2, 9), + field: "", + operator: "", + value: "", + }); + console.log("new constraint", criteria); + setFormState({ + ...formState, + criteria, + }); + } + + function validateCriteria(criteria: ConstraintCriteria[]) { + let response = null; + if (!criteria) response = "Criteria is required"; + if (criteria && criteria.length === 0) response = "At least one criteria is required"; + if (criteria && criteria.length > 10) response = "Maximum 10 criteria are allowed"; + const crit = + criteria && + criteria + .map((c) => { + console.log("c", c); + if (!c.field) return "Field is required"; + if (!c.operator) return "Operator is required"; + if (!c.value) return "Value is required"; + return null; + }) + .filter((x) => x !== null); + if (crit && crit.length > 0) response = crit.join(", "); + console.log("criteria eval", response); + return response; + } + + function validateGroupPermissions(groupPermissions: GroupPermission[]): React.ReactNode { + let response = null; + if (!groupPermissions) response = "At least one group permission is required"; + const gp = + groupPermissions && + groupPermissions + .map((gp) => { + if (!gp.groupId) return "Group Id is required"; + if (!gp.permission) return "Permission is required"; + return null; + }) + .filter((x) => x !== null); + if (gp && gp.length > 0) response = gp.join(", "); + + // count the occurrences of each groupId + const groupCounts = + groupPermissions && + groupPermissions + .map((gp) => { + return gp.groupId; + }) + .filter((x) => x !== null) + .reduce((acc: { [key: string]: number }, curr: string) => { + if (acc[curr]) { + acc[curr]++; + } else { + acc[curr] = 1; + } + return acc; + }, {}); + + if (groupCounts) { + const violations = Object.keys(groupCounts).filter((key) => groupCounts[key] > 1); + if (violations.length > 0) { + response = + "Groups cannot have duplicate permissions. The following groups should consolidate: " + + violations.join(", "); + } + } + + return response; + } + + return ( + setOpen(false)} + size="large" + header={`${createOrUpdate} Constraint`} + footer={ + + + + + + + + } + > +
+ + + + + + + setFormState({ ...formState, name: detail.value }) + } + placeholder="Constraint Name" + data-testid="constraint-name" + /> + + +