diff --git a/.github/workflows/buildpipeline.yaml b/.github/workflows/ci.yaml similarity index 67% rename from .github/workflows/buildpipeline.yaml rename to .github/workflows/ci.yaml index e8a4745c2..9d7ab6592 100644 --- a/.github/workflows/buildpipeline.yaml +++ b/.github/workflows/ci.yaml @@ -1,24 +1,27 @@ name: CI - on: - push: - pull_request: - types: [opened, reopened] - + push: + pull_request: + types: [opened, reopened] concurrency: group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' cancel-in-progress: true - - jobs: Security: name: Security Pipeline uses: uc-cdis/.github/.github/workflows/securitypipeline.yaml@master secrets: inherit + UnitTest: + name: Python Unit Test with Postgres + uses: uc-cdis/.github/.github/workflows/python_unit_test.yaml@master + with: + python-version: '3.9' + test-script: 'tests/ci_commands_script.sh' + run-coveralls: true ci: name: Build Image and Push - # TODO Add this line back once we update to Python 3.9 from 3.6 + # TODO Uncomment after PXP-9212 # needs: Security uses: uc-cdis/.github/.github/workflows/image_build_push.yaml@master secrets: diff --git a/.secrets.baseline b/.secrets.baseline index 8078c23c4..8c97f29fb 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -115,13 +115,13 @@ } ], "results": { - ".github/workflows/buildpipeline.yaml": [ + ".github/workflows/ci.yaml": [ { "type": "Secret Keyword", - "filename": ".github/workflows/buildpipeline.yaml", + "filename": ".github/workflows/ci.yaml", "hashed_secret": "3e26d6750975d678acb8fa35a0f69237881576b0", "is_verified": false, - "line_number": 17 + "line_number": 13 } ], "deployment/scripts/postgresql/postgresql_init.sql": [ @@ -210,13 +210,22 @@ "line_number": 137 } ], + "fence/resources/storage/storageclient/cleversafe.py": [ + { + "type": "Secret Keyword", + "filename": "fence/resources/storage/storageclient/cleversafe.py", + "hashed_secret": "7cb6efb98ba5972a9b5090dc2e517fe14d12cb04", + "is_verified": false, + "line_number": 274 + } + ], "fence/utils.py": [ { "type": "Secret Keyword", "filename": "fence/utils.py", "hashed_secret": "8318df9ecda039deac9868adf1944a29a95c7114", "is_verified": false, - "line_number": 128 + "line_number": 129 } ], "migrations/versions/a04a70296688_non_unique_client_name.py": [ @@ -259,14 +268,14 @@ "filename": "tests/conftest.py", "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", "is_verified": false, - "line_number": 1559 + "line_number": 1561 }, { "type": "Base64 High Entropy String", "filename": "tests/conftest.py", "hashed_secret": "227dea087477346785aefd575f91dd13ab86c108", "is_verified": false, - "line_number": 1582 + "line_number": 1583 } ], "tests/credentials/google/test_credentials.py": [ @@ -385,6 +394,24 @@ "line_number": 300 } ], + "tests/storageclient/storage_client_mock.py": [ + { + "type": "Secret Keyword", + "filename": "tests/storageclient/storage_client_mock.py", + "hashed_secret": "37bbea9557f9efd1eeadb25dda9ab6514f08fde9", + "is_verified": false, + "line_number": 158 + } + ], + "tests/storageclient/test_cleversafe_api_client.py": [ + { + "type": "Secret Keyword", + "filename": "tests/storageclient/test_cleversafe_api_client.py", + "hashed_secret": "f683c485d521c2e45830146dd570111770baea29", + "is_verified": false, + "line_number": 130 + } + ], "tests/test-fence-config.yaml": [ { "type": "Basic Auth Credentials", @@ -395,5 +422,5 @@ } ] }, - "generated_at": "2023-11-16T21:15:57Z" + "generated_at": "2024-03-16T00:09:27Z" } diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9dfb763ba..000000000 --- a/.travis.yml +++ /dev/null @@ -1,51 +0,0 @@ -language: python -dist: jammy -python: - - "3.9" - -sudo: false - -cache: pip - -services: - - postgresql - -addons: - postgresql: "13" - apt: - sources: - - sourceline: deb http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main - 13 - key_url: https://www.postgresql.org/media/keys/ACCC4CF8.asc - packages: - - postgresql-13 - -before_install: - # Copy custom configs from the repo because PG-13 isn't set up to run like - # it normally does on Travis out of the box. - # Source: https://github.com/NCI-GDC/psqlgraph/blob/94f315db2c039217752cba85d9c63988f2059317/.travis.yml - - sudo cp travis/postgresql.conf /etc/postgresql/13/main/postgresql.conf - - sudo cp travis/pg_hba.conf /etc/postgresql/13/main/pg_hba.conf - - sudo pg_ctlcluster 13 main restart - -install: - - pip install --upgrade pip - - pip install poetry - - poetry install --all-extras -vv --no-interaction - - poetry show -vv - - psql -c 'SELECT version();' -U postgres - - psql -U postgres -c "create database fence_test_tmp" - - pip list - -before_script: - - sudo rm -f /etc/boto.cfg - - mkdir -p tests/resources/keys; cd tests/resources/keys; openssl genrsa -out test_private_key.pem 2048; openssl rsa -in test_private_key.pem -pubout -out test_public_key.pem - - openssl genrsa -out test_private_key_2.pem 2048; openssl rsa -in test_private_key_2.pem -pubout -out test_public_key_2.pem - - cd - - -script: - - poetry run pytest -vv --cov=fence --cov=migrations/versions --cov-report xml tests - -after_script: - - python-codacy-coverage -r coverage.xml - - COVERALLS_REPO_TOKEN=$COVERALLS_TOKEN coveralls diff --git a/bin/fence_create.py b/bin/fence_create.py index 7adaedd82..2c2d6a718 100755 --- a/bin/fence_create.py +++ b/bin/fence_create.py @@ -329,6 +329,9 @@ def parse_arguments(): help='scopes to include in the token (e.g. "user" or "data")', ) token_create.add_argument("--exp", help="time in seconds until token expiration") + token_create.add_argument( + "--client_id", help="Client Id, required to generate refresh token" + ) force_link_google = subparsers.add_parser("force-link-google") force_link_google.add_argument( @@ -581,6 +584,7 @@ def main(): username=args.username, scopes=args.scopes, expires_in=args.exp, + client_id=args.client_id, ) token_type = str(args.type).strip().lower() if token_type == "access_token" or token_type == "access": diff --git a/docs/base_user.yaml b/docs/base_user.yaml index 7c8e83c35..dcd824def 100644 --- a/docs/base_user.yaml +++ b/docs/base_user.yaml @@ -78,14 +78,16 @@ authz: - /programs - id: open_data_reader role_ids: - - reader - - storage_reader + - peregrine_reader + - guppy_reader + - fence_storage_reader resource_paths: - /open - id: all_programs_reader role_ids: - - reader - - storage_reader + - peregrine_reader + - guppy_reader + - fence_storage_reader resource_paths: - /programs - id: MyFirstProject_submitter @@ -168,6 +170,24 @@ authz: action: service: '*' method: read-storage + - id: peregrine_reader + permissions: + - id: peregrine_reader + action: + method: read + service: peregrine + - id: guppy_reader + permissions: + - id: guppy_reader + action: + method: read + service: guppy + - id: fence_storage_reader + permissions: + - id: fence_storage_reader + action: + method: read-storage + service: fence clients: wts: diff --git a/docs/google_architecture.md b/docs/google_architecture.md index 49c39997a..b00012ff0 100644 --- a/docs/google_architecture.md +++ b/docs/google_architecture.md @@ -17,7 +17,7 @@ We'll talk about each one of those in-depth here (and even delve into the intern ### Fence -> cirrus -> Google: A library wrapping Google's API -We have a library that wraps Google's public API called [cirrus](https://github.com/uc-cdis/cirrus). Our design is such that fence does not hit Google's API directly, but goes through cirrus. For all of cirrus's features to work, a very specific setup is required, which is detailed in cirrus's README. +We have a library that wraps Google's public API called [cirrus](https://github.com/uc-cdis/cirrus). Our design is such that fence does not hit Google's API directly, but goes through gen3cirrus. For all of cirrus's features to work, a very specific setup is required, which is detailed in cirrus's README. Essentially, cirrus requires a Google Cloud Identity account (for group management) and Google Cloud Platform project(s). In order to automate group management in Google Cloud Identity with cirrus, you must go through a manual process of allowing API access and delegating a specific service account from a Google Cloud Platform project to have group management authority. Details can be found in cirrus's README. diff --git a/fence/__init__.py b/fence/__init__.py index ea58cf02c..050a4f82d 100755 --- a/fence/__init__.py +++ b/fence/__init__.py @@ -5,7 +5,7 @@ import flask from flask_cors import CORS from sqlalchemy.orm import scoped_session -from flask import _app_ctx_stack, current_app +from flask import current_app from werkzeug.local import LocalProxy from authutils.oauth2.client import OAuthClient @@ -364,7 +364,6 @@ def app_config( _setup_audit_service_client(app) _setup_data_endpoint_and_boto(app) _load_keys(app, root_dir) - _set_authlib_cfgs(app) app.prometheus_counters = {} if config["ENABLE_PROMETHEUS_METRICS"]: @@ -407,24 +406,6 @@ def _load_keys(app, root_dir): } -def _set_authlib_cfgs(app): - # authlib OIDC settings - # key will need to be added - settings = {"OAUTH2_JWT_KEY": keys.default_private_key(app)} - app.config.update(settings) - config.update(settings) - - # only add the following if not already provided - config.setdefault("OAUTH2_JWT_ENABLED", True) - config.setdefault("OAUTH2_JWT_ALG", "RS256") - config.setdefault("OAUTH2_JWT_ISS", app.config["BASE_URL"]) - config.setdefault("OAUTH2_PROVIDER_ERROR_URI", "/api/oauth2/errors") - app.config.setdefault("OAUTH2_JWT_ENABLED", True) - app.config.setdefault("OAUTH2_JWT_ALG", "RS256") - app.config.setdefault("OAUTH2_JWT_ISS", app.config["BASE_URL"]) - app.config.setdefault("OAUTH2_PROVIDER_ERROR_URI", "/api/oauth2/errors") - - def _setup_oidc_clients(app): configured_idps = config.get("OPENID_CONNECT", {}) @@ -482,7 +463,10 @@ def _setup_oidc_clients(app): logger=logger, ) elif idp == "fence": - app.fence_client = OAuthClient(**settings) + # https://docs.authlib.org/en/latest/client/flask.html#flask-client + app.fence_client = OAuthClient(app) + # https://docs.authlib.org/en/latest/client/frameworks.html + app.fence_client.register(**settings) else: # generic OIDC implementation client = Oauth2ClientBase( settings=settings, diff --git a/fence/auth.py b/fence/auth.py index 3bc1873f3..e23ada890 100644 --- a/fence/auth.py +++ b/fence/auth.py @@ -35,7 +35,10 @@ def get_jwt(): try: bearer, token = header.split(" ") except ValueError: - raise Unauthorized("authorization header not in expected format") + msg = "authorization header not in expected format" + logger.debug(f"{msg}. Received header: {header}") + logger.error(f"{msg}.") + raise Unauthorized(msg) if bearer.lower() != "bearer": raise Unauthorized("expected bearer token in auth header") return token diff --git a/fence/blueprints/data/indexd.py b/fence/blueprints/data/indexd.py index 09c81f26e..f7b9488f6 100755 --- a/fence/blueprints/data/indexd.py +++ b/fence/blueprints/data/indexd.py @@ -6,8 +6,8 @@ from sqlalchemy.sql.functions import user from cached_property import cached_property -import cirrus -from cirrus import GoogleCloudManager +import gen3cirrus +from gen3cirrus import GoogleCloudManager from cdislogging import get_logger from cdispyutils.config import get_value from cdispyutils.hmac4 import generate_aws_presigned_url @@ -82,7 +82,7 @@ def get_signed_url_for_file( r_pays_project = flask.request.args.get("userProject", None) db_session = db_session or current_app.scoped_session() - # default to signing the url + # default to signing the URL force_signed_url = True no_force_sign_param = flask.request.args.get("no_force_sign") if no_force_sign_param and no_force_sign_param.lower() == "true": @@ -162,19 +162,22 @@ def get_signed_url_for_file( _log_signed_url_data_info( indexed_file=indexed_file, user_sub=flask.g.audit_data.get("sub", ""), - requested_protocol=requested_protocol + client_id=_get_client_id(), + requested_protocol=requested_protocol, ) return {"url": signed_url} -def _log_signed_url_data_info(indexed_file, user_sub, requested_protocol): - size_in_kibibytes = indexed_file.index_document.get("size", 0) / 1024 +def _log_signed_url_data_info(indexed_file, user_sub, client_id, requested_protocol): + size_in_kibibytes = (indexed_file.index_document.get("size") or 0) / 1024 acl = indexed_file.index_document.get("acl") authz = indexed_file.index_document.get("authz") - # the behavior later on is to pick the 1st location as the signed URL if a protocol is not requested - protocol = requested_protocol or indexed_file.indexed_file_locations[0].protocol + # the behavior later on is to pick the 1st location as the signed URL if a protocol is not requested, if available + protocol = requested_protocol + if not protocol and indexed_file.indexed_file_locations: + protocol = indexed_file.indexed_file_locations[0].protocol # figure out which bucket was used based on the protocol bucket = "" @@ -190,10 +193,21 @@ def _log_signed_url_data_info(indexed_file, user_sub, requested_protocol): break logger.info( - f"Signed URL Generated. size_in_kibibytes={size_in_kibibytes} acl={acl} authz={authz} bucket={bucket} user_sub={user_sub}" + f"Signed URL Generated. size_in_kibibytes={size_in_kibibytes} " + f"acl={acl} authz={authz} bucket={bucket} user_sub={user_sub} client_id={client_id}" ) +def _get_client_id(): + client_id = "Unknown Client" + + try: + client_id = current_token.get("azp") or "Unknown Client" + except Exception as exc: + pass + + return client_id + def prepare_presigned_url_audit_log(protocol, indexed_file): """ Store in `flask.g.audit_data` the data needed to record an audit log. @@ -319,12 +333,7 @@ def make_signed_url(self, file_name, protocol=None, expires_in=None, bucket=None ) else: if not bucket: - try: - bucket = flask.current_app.config["DATA_UPLOAD_BUCKET"] - except KeyError: - raise InternalError( - "fence not configured with data upload bucket; can't create signed URL" - ) + bucket = flask.current_app.config["DATA_UPLOAD_BUCKET"] self.logger.debug("Attempting to upload to bucket '{}'".format(bucket)) s3_url = "s3://{}/{}/{}".format(bucket, self.guid, file_name) @@ -349,13 +358,8 @@ def init_multipart_upload(key, expires_in=None, bucket=None): Returns: uploadId(str) """ - try: - bucket = bucket or flask.current_app.config["DATA_UPLOAD_BUCKET"] - except KeyError: - raise InternalError( - "fence not configured with data upload bucket; can't create signed URL" - ) - + if not bucket: + bucket = flask.current_app.config["DATA_UPLOAD_BUCKET"] s3_url = "s3://{}/{}".format(bucket, key) return S3IndexedFileLocation(s3_url).init_multipart_upload(expires_in) @@ -376,12 +380,7 @@ def complete_multipart_upload(key, uploadId, parts, expires_in=None, bucket=None if bucket: verify_data_upload_bucket_configuration(bucket) else: - try: - bucket = flask.current_app.config["DATA_UPLOAD_BUCKET"] - except KeyError: - raise InternalError( - "fence not configured with data upload bucket; can't create signed URL" - ) + bucket = flask.current_app.config["DATA_UPLOAD_BUCKET"] s3_url = "s3://{}/{}".format(bucket, key) S3IndexedFileLocation(s3_url).complete_multipart_upload( uploadId, parts, expires_in @@ -405,12 +404,7 @@ def generate_aws_presigned_url_for_part( if bucket: verify_data_upload_bucket_configuration(bucket) else: - try: - bucket = flask.current_app.config["DATA_UPLOAD_BUCKET"] - except KeyError: - raise InternalError( - "fence not configured with data upload bucket; can't create signed URL" - ) + bucket = flask.current_app.config["DATA_UPLOAD_BUCKET"] s3_url = "s3://{}/{}".format(bucket, key) return S3IndexedFileLocation(s3_url).generate_presigned_url_for_part_upload( uploadId, partNumber, expires_in @@ -430,7 +424,7 @@ class IndexedFile(object): also be cleaner). Args: - file_id (str): GUID for the file. + file_id (str): GUID for the file """ def __init__(self, file_id): @@ -1204,7 +1198,7 @@ def _generate_anonymous_google_storage_signed_url( ): # we will use the main fence SA service account to sign anonymous requests private_key = get_google_app_creds() - final_url = cirrus.google_cloud.utils.get_signed_url( + final_url = gen3cirrus.google_cloud.utils.get_signed_url( resource_path, http_verb, expires_in, @@ -1345,7 +1339,7 @@ def _generate_google_storage_signed_url( if config["BILLING_PROJECT_FOR_SIGNED_URLS"] and not r_pays_project: r_pays_project = config["BILLING_PROJECT_FOR_SIGNED_URLS"] - final_url = cirrus.google_cloud.utils.get_signed_url( + final_url = gen3cirrus.google_cloud.utils.get_signed_url( resource_path, http_verb, expires_in, @@ -1658,22 +1652,7 @@ def filter_auth_ids(action, list_auth_ids): def verify_data_upload_bucket_configuration(bucket): - """ - Verify that the bucket is configured in Fence as an uploadable bucket - - Args: - bucket(str): bucket name - """ s3_buckets = flask.current_app.config["ALLOWED_DATA_UPLOAD_BUCKETS"] - - if not s3_buckets: - raise InternalError("ALLOWED_DATA_UPLOAD_BUCKETS not configured") - - s3_buckets = get_value( - flask.current_app.config, - "ALLOWED_DATA_UPLOAD_BUCKETS", - InternalError("ALLOWED_DATA_UPLOAD_BUCKETS not configured"), - ) if bucket not in s3_buckets: logger.error(f"Bucket '{bucket}' not in ALLOWED_DATA_UPLOAD_BUCKETS config") logger.debug(f"Buckets configgured in ALLOWED_DATA_UPLOAD_BUCKETS {s3_buckets}") diff --git a/fence/blueprints/data/multipart_upload.py b/fence/blueprints/data/multipart_upload.py index 4adc7f5b3..7352f66f1 100644 --- a/fence/blueprints/data/multipart_upload.py +++ b/fence/blueprints/data/multipart_upload.py @@ -39,7 +39,6 @@ def initialize_multipart_upload(bucket_name, key, credentials): aws_secret_access_key=credentials["aws_secret_access_key"], aws_session_token=credentials.get("aws_session_token"), ) - s3client = None if url: s3client = session.client("s3", endpoint_url=url) @@ -94,7 +93,6 @@ def complete_multipart_upload(bucket_name, key, credentials, uploadId, parts): aws_secret_access_key=credentials["aws_secret_access_key"], aws_session_token=credentials.get("aws_session_token"), ) - s3client = None if url: s3client = session.client("s3", endpoint_url=url) @@ -147,7 +145,12 @@ def generate_presigned_url_for_uploading_part( ) bucket = s3_buckets.get(bucket_name) - if s3_buckets.get("endpoint_url"): + s3_buckets = get_value( + config, "S3_BUCKETS", InternalError("S3_BUCKETS not configured") + ) + bucket = s3_buckets.get(bucket_name) + + if bucket.get("endpoint_url"): url = bucket["endpoint_url"].strip("/") + "/{}/{}".format( bucket_name, key.strip("/") ) @@ -156,9 +159,10 @@ def generate_presigned_url_for_uploading_part( additional_signed_qs = {"partNumber": str(partNumber), "uploadId": uploadId} try: - return generate_aws_presigned_url( + presigned_url = generate_aws_presigned_url( url, "PUT", credentials, "s3", region, expires, additional_signed_qs ) + return presigned_url except Exception as e: raise InternalError( "Can not generate presigned url for part number {} of key {}. Detail {}".format( diff --git a/fence/blueprints/google.py b/fence/blueprints/google.py index a3c8b2348..3e3def67d 100644 --- a/fence/blueprints/google.py +++ b/fence/blueprints/google.py @@ -7,9 +7,9 @@ import flask from flask_restful import Resource -from cirrus import GoogleCloudManager -from cirrus.errors import CirrusNotFound -from cirrus.google_cloud.errors import GoogleAPIError +from gen3cirrus import GoogleCloudManager +from gen3cirrus.errors import CirrusNotFound +from gen3cirrus.google_cloud.errors import GoogleAPIError from fence.auth import current_token, require_auth_header from fence.restful import RestfulApi diff --git a/fence/blueprints/link.py b/fence/blueprints/link.py index 99ac4c9fa..a9854f067 100644 --- a/fence/blueprints/link.py +++ b/fence/blueprints/link.py @@ -6,7 +6,7 @@ from cdislogging import get_logger -from cirrus import GoogleCloudManager +from gen3cirrus import GoogleCloudManager from fence.blueprints.login.redirect import validate_redirect from fence.restful import RestfulApi from fence.errors import NotFound diff --git a/fence/blueprints/login/fence_login.py b/fence/blueprints/login/fence_login.py index 7efd49520..13b4de8c8 100644 --- a/fence/blueprints/login/fence_login.py +++ b/fence/blueprints/login/fence_login.py @@ -30,19 +30,22 @@ def __init__(self): def get(self): """Handle ``GET /login/fence``.""" - oauth2_redirect_uri = flask.current_app.fence_client.client_kwargs.get( - "redirect_uri" - ) + + # OAuth class can have mutliple clients + client = flask.current_app.fence_client._clients[ + flask.current_app.config["OPENID_CONNECT"]["fence"]["name"] + ] + + oauth2_redirect_uri = client.client_kwargs.get("redirect_uri") + redirect_url = flask.request.args.get("redirect") if redirect_url: validate_redirect(redirect_url) flask.session["redirect"] = redirect_url - ( - authorization_url, - state, - ) = flask.current_app.fence_client.generate_authorize_redirect( - oauth2_redirect_uri, prompt="login" - ) + + rv = client.create_authorization_url(oauth2_redirect_uri, prompt="login") + + authorization_url = rv["url"] # add idp parameter to the authorization URL if "idp" in flask.request.args: @@ -57,7 +60,7 @@ def get(self): flask.session["shib_idp"] = shib_idp authorization_url = add_params_to_uri(authorization_url, params) - flask.session["state"] = state + flask.session["state"] = rv["state"] return flask.redirect(authorization_url) @@ -88,16 +91,19 @@ def get(self): " login page for the original application to continue." ) # Get the token response and log in the user. - redirect_uri = flask.current_app.fence_client._get_session().redirect_uri - tokens = flask.current_app.fence_client.fetch_access_token( - redirect_uri, **flask.request.args.to_dict() + client_name = config["OPENID_CONNECT"]["fence"].get("name", "fence") + client = flask.current_app.fence_client._clients[client_name] + oauth2_redirect_uri = client.client_kwargs.get("redirect_uri") + + tokens = client.fetch_access_token( + oauth2_redirect_uri, **flask.request.args.to_dict() ) try: # For multi-Fence setup with two Fences >=5.0.0 id_token_claims = validate_jwt( tokens["id_token"], - aud=self.client.client_id, + aud=client.client_id, scope={"openid"}, purpose="id", attempt_refresh=True, diff --git a/fence/blueprints/login/utils.py b/fence/blueprints/login/utils.py index 4b189977e..3dfca2eae 100644 --- a/fence/blueprints/login/utils.py +++ b/fence/blueprints/login/utils.py @@ -21,7 +21,10 @@ def allowed_login_redirects(): with flask.current_app.db.session as session: clients = session.query(Client).all() for client in clients: - allowed.extend(client.redirect_uris) + if isinstance(client.redirect_uris, list): + allowed.extend(client.redirect_uris) + elif isinstance(client.redirect_uris, str): + allowed.append(client.redirect_uris) return {domain(url) for url in allowed} diff --git a/fence/blueprints/oauth2.py b/fence/blueprints/oauth2.py index d79f106aa..0d1428cf5 100644 --- a/fence/blueprints/oauth2.py +++ b/fence/blueprints/oauth2.py @@ -32,9 +32,14 @@ from fence.utils import clear_cookies from fence.user import get_current_user from fence.config import config - +from authlib.oauth2.rfc6749.errors import ( + InvalidScopeError, +) +from fence.utils import validate_scopes +from cdislogging import get_logger blueprint = flask.Blueprint("oauth2", __name__) +logger = get_logger(__name__) @blueprint.route("/authorize", methods=["GET", "POST"]) @@ -114,7 +119,7 @@ def authorize(*args, **kwargs): return flask.redirect(login_url) try: - grant = server.validate_consent_request(end_user=user) + grant = server.get_consent_grant(end_user=user) except OAuth2Error as e: raise Unauthorized("Failed to authorize: {}".format(str(e))) @@ -122,6 +127,13 @@ def authorize(*args, **kwargs): with flask.current_app.db.session as session: client = session.query(Client).filter_by(client_id=client_id).first() + # Need to do scope check here now due to our design of putting allowed_scope on client + # Authlib now put allowed scope on OIDC server side which doesn't work with our design without modification to the lib + # Doing the scope check here because both client and grant is available here + # Either Get or Post request + request_scopes = flask.request.args.get("scope") or flask.request.form.get("scope") + validate_scopes(request_scopes, client) + # TODO: any way to get from grant? confirm = flask.request.form.get("confirm") or flask.request.args.get("confirm") if client.auto_approve: diff --git a/fence/blueprints/storage_creds/google.py b/fence/blueprints/storage_creds/google.py index b9c67fee3..49c4cadd4 100644 --- a/fence/blueprints/storage_creds/google.py +++ b/fence/blueprints/storage_creds/google.py @@ -6,8 +6,8 @@ from flask_restful import Resource from flask import current_app -from cirrus import GoogleCloudManager -from cirrus.config import config as cirrus_config +from gen3cirrus import GoogleCloudManager +from gen3cirrus.config import config as cirrus_config from fence.config import config from fence.auth import require_auth_header diff --git a/fence/config-default.yaml b/fence/config-default.yaml index 279c048fb..a73ebb976 100755 --- a/fence/config-default.yaml +++ b/fence/config-default.yaml @@ -138,6 +138,9 @@ OPENID_CONNECT: # If this fence instance is a client of another fence, fill this cfg out. # REMOVE if not needed fence: + # Custom name to display for consent screens. If not provided, will use `fence`. + # If the other fence is using NIH Login, you should make name: `NIH Login` + name: '' # this api_base_url should be the root url for the OTHER fence # something like: https://example.com api_base_url: '' @@ -155,9 +158,6 @@ OPENID_CONNECT: authorize_url: '{{api_base_url}}/oauth2/authorize' access_token_url: '{{api_base_url}}/oauth2/token' refresh_token_url: '{{api_base_url}}/oauth2/token' - # Custom name to display for consent screens. If not provided, will use `fence`. - # If the other fence is using NIH Login, you should make name: `NIH Login` - name: '' # if mock is true, will fake a successful login response for login # WARNING: DO NOT ENABLE IN PRODUCTION (for testing purposes only) mock: false @@ -386,16 +386,9 @@ ENABLED_IDENTITY_PROVIDERS: {} # ////////////////////////////////////////////////////////////////////////////////////// -# LIBRARY CONFIGURATION (authlib & flask) +# LIBRARY CONFIGURATION (flask) # - Already contains reasonable defaults # ////////////////////////////////////////////////////////////////////////////////////// -# authlib-specific configs for OIDC flow and JWTs -# NOTE: the OAUTH2_JWT_KEY cfg gets set automatically by fence if keys are setup -# correctly -OAUTH2_JWT_ALG: 'RS256' -OAUTH2_JWT_ENABLED: true -OAUTH2_JWT_ISS: '{{BASE_URL}}' -OAUTH2_PROVIDER_ERROR_URI: '/api/oauth2/errors' # used for flask, "path mounted under by the application / web server" # since we deploy as microservices, fence is typically under {{base}}/user @@ -691,6 +684,10 @@ GS_BUCKETS: {} # bucket3: # region: 'us-east-1' +# When using the Cleversafe storageclient, whether or not to send verify=true +# for requests +VERIFY_CLEVERSAFE_CERT: true + # Names of the S3 buckets to which data files can be uploaded. They should be # configured in `S3_BUCKETS`. ALLOWED_DATA_UPLOAD_BUCKETS: [] diff --git a/fence/config.py b/fence/config.py index b4deeeb22..d981bfd38 100644 --- a/fence/config.py +++ b/fence/config.py @@ -2,7 +2,7 @@ from yaml import safe_load as yaml_load import urllib.parse -import cirrus +import gen3cirrus from gen3config import Config from cdislogging import get_logger @@ -46,6 +46,7 @@ def post_process(self): "CIRRUS_CFG", "WHITE_LISTED_GOOGLE_PARENT_ORGS", "CLIENT_CREDENTIALS_ON_DOWNLOAD_ENABLED", + "DATA_UPLOAD_BUCKET", ] for default in defaults: self.force_default_if_none(default, default_cfg=default_config) @@ -92,7 +93,7 @@ def post_process(self): if self._configs.get("MOCK_STORAGE", False): self._configs["STORAGE_CREDENTIALS"] = {} - cirrus.config.config.update(**self._configs.get("CIRRUS_CFG", {})) + gen3cirrus.config.config.update(**self._configs.get("CIRRUS_CFG", {})) # if we have a default google project for billing requester pays, we should # NOT allow end-users to have permission to create Temporary Google Service diff --git a/fence/jwt/utils.py b/fence/jwt/utils.py index eada044a7..be6c60f4b 100644 --- a/fence/jwt/utils.py +++ b/fence/jwt/utils.py @@ -1,8 +1,13 @@ import flask +from cdislogging import get_logger + from fence.errors import Unauthorized +logger = get_logger(__name__) + + def get_jwt_header(): """ Get the user's JWT from the Authorization header, or raise Unauthorized on failure. @@ -18,5 +23,8 @@ def get_jwt_header(): try: jwt = header.split(" ")[1] except IndexError: - raise Unauthorized("authorization header missing token") + msg = "authorization header missing token" + logger.debug(f"{msg}. Received header: {header}") + logger.error(f"{msg}.") + raise Unauthorized(msg) return jwt diff --git a/fence/models.py b/fence/models.py index 9122e874c..779d27519 100644 --- a/fence/models.py +++ b/fence/models.py @@ -9,7 +9,13 @@ from enum import Enum -from authlib.flask.oauth2.sqla import OAuth2AuthorizationCodeMixin, OAuth2ClientMixin +from authlib.integrations.sqla_oauth2 import ( + OAuth2AuthorizationCodeMixin, + OAuth2ClientMixin, +) + +import time +import json import bcrypt from datetime import datetime, timedelta import flask @@ -150,7 +156,7 @@ def get_client_expires_at(expires_in, grant_types): # `timestamp()` already converts to UTC expires_at = (datetime.now() + timedelta(days=expires_in)).timestamp() - if "client_credentials" in grant_types.split("\n"): + if "client_credentials" in grant_types: if not expires_in or expires_in <= 0 or expires_in > 366: logger.warning( "Credentials with the 'client_credentials' grant which will be used externally are required to expire within 12 months. Use the `--expires-in` parameter to add an expiration." @@ -188,9 +194,9 @@ class Client(Base, OAuth2ClientMixin): __tablename__ = "client" - client_id = Column(String(40), primary_key=True) + client_id = Column(String(48), primary_key=True, index=True) # this is hashed secret - client_secret = Column(String(60), unique=True, index=True, nullable=True) + client_secret = Column(String(120), unique=True, index=True, nullable=True) # human readable name name = Column(String(40), nullable=False) @@ -211,46 +217,51 @@ class Client(Base, OAuth2ClientMixin): # public or confidential is_confidential = Column(Boolean, default=True) - # NOTE: DEPRECATED - # Client now uses `redirect_uri` column, from authlib client model - _redirect_uris = Column(Text) - - _allowed_scopes = Column(Text, nullable=False, default="") + expires_at = Column(Integer, nullable=False, default=0) + # Deprecated, keeping these around in case it is needed later _default_scopes = Column(Text) _scopes = ["compute", "storage", "user"] - expires_at = Column(Integer, nullable=False, default=0) + def __init__(self, client_id, expires_in=0, **kwargs): - # note that authlib adds a response_type column which is not used here + # New Json object for Authlib Oauth client + if "_client_metadata" in kwargs: + client_metadata = json.loads(kwargs.pop("_client_metadata")) + else: + client_metadata = {} - def __init__(self, client_id, expires_in=0, **kwargs): - """ - NOTE that for authlib, the client must have an attribute ``redirect_uri`` which - is a newline-delimited list of valid redirect URIs. - """ if "allowed_scopes" in kwargs: allowed_scopes = kwargs.pop("allowed_scopes") if isinstance(allowed_scopes, list): - kwargs["_allowed_scopes"] = " ".join(allowed_scopes) + client_metadata["scope"] = " ".join(allowed_scopes) else: - kwargs["_allowed_scopes"] = allowed_scopes + client_metadata["scope"] = allowed_scopes + + # redirect uri is now part of authlibs client_metadata if "redirect_uris" in kwargs: redirect_uris = kwargs.pop("redirect_uris") if isinstance(redirect_uris, list): - kwargs["redirect_uri"] = "\n".join(redirect_uris) + # redirect_uris is now part of the metadata json object + client_metadata["redirect_uris"] = redirect_uris + elif redirect_uris: + client_metadata["redirect_uris"] = [redirect_uris] else: - kwargs["redirect_uri"] = redirect_uris + client_metadata["redirect_uris"] = [] + # default grant types to allow for auth code flow and resfreshing grant_types = kwargs.pop("grant_types", None) or [ GrantType.code.value, GrantType.refresh.value, ] + # grant types is now part of authlibs client_metadata if isinstance(grant_types, list): - kwargs["grant_type"] = "\n".join(grant_types) + client_metadata["grant_types"] = grant_types + elif grant_types: + # assume it's already in correct format and make it a list + client_metadata["grant_types"] = [grant_types] else: - # assume it's already in correct format - kwargs["grant_type"] = grant_types + client_metadata["grant_types"] = [] supported_grant_types = [ "authorization_code", @@ -260,28 +271,50 @@ def __init__(self, client_id, expires_in=0, **kwargs): ] assert all( grant_type in supported_grant_types - for grant_type in kwargs["grant_type"].split("\n") - ), f"Grant types '{kwargs['grant_type']}' are not in supported types {supported_grant_types}" + for grant_type in client_metadata["grant_types"] + ), f"Grant types '{client_metadata['grant_types']}' are not in supported types {supported_grant_types}" - if "authorization_code" in kwargs["grant_type"].split("\n"): + if "authorization_code" in client_metadata["grant_types"]: assert kwargs.get("user") or kwargs.get( "user_id" ), "A username is required for the 'authorization_code' grant" - assert kwargs.get( - "redirect_uri" + assert client_metadata.get( + "redirect_uris" ), "Redirect URL(s) are required for the 'authorization_code' grant" - expires_at = get_client_expires_at( - expires_in=expires_in, grant_types=kwargs["grant_type"] - ) - if expires_at: - kwargs["expires_at"] = expires_at + # response_types is now part of authlib's client_metadata + response_types = kwargs.pop("response_types", None) + if isinstance(response_types, list): + client_metadata["response_types"] = "\n".join(response_types) + elif response_types: + # assume it's already in correct format + client_metadata["response_types"] = [response_types] + else: + client_metadata["response_types"] = [] + + if "token_endpoint_auth_method" in kwargs: + client_metadata["token_endpoint_auth_method"] = kwargs.pop( + "token_endpoint_auth_method" + ) + + # Do this if expires_in is specified or expires_at is not supplied + if expires_in != 0 or ("expires_at" not in kwargs): + expires_at = get_client_expires_at( + expires_in=expires_in, grant_types=client_metadata["grant_types"] + ) + if expires_at: + kwargs["expires_at"] = expires_at + + if "client_id_issued_at" not in kwargs or kwargs["client_id_issued_at"] is None: + kwargs["client_id_issued_at"] = int(time.time()) + + kwargs["_client_metadata"] = json.dumps(client_metadata) super(Client, self).__init__(client_id=client_id, **kwargs) @property def allowed_scopes(self): - return self._allowed_scopes.split(" ") + return self.scope.split(" ") @property def client_type(self): @@ -295,16 +328,6 @@ def client_type(self): return "public" return "confidential" - @property - def default_redirect_uri(self): - return self.redirect_uris[0] - - @property - def default_scopes(self): - if self._default_scopes: - return self._default_scopes.split() - return [] - @staticmethod def get_by_client_id(client_id): with flask.current_app.db.session as session: @@ -327,18 +350,18 @@ def check_requested_scopes(self, scopes): return False return set(self.allowed_scopes).issuperset(scopes) - def check_token_endpoint_auth_method(self, method): + # Replaces Authlib method. Our logic does not actually look at token_auth_endpoint value + def check_endpoint_auth_method(self, method, endpoint): """ Only basic auth is supported. If anything else gets added, change this """ - protected_types = [ClientAuthType.basic.value, ClientAuthType.post.value] - return (self.is_confidential and method in protected_types) or ( - not self.is_confidential and method == ClientAuthType.none.value - ) + if endpoint == "token": + protected_types = [ClientAuthType.basic.value, ClientAuthType.post.value] + return (self.is_confidential and method in protected_types) or ( + not self.is_confidential and method == ClientAuthType.none.value + ) - def validate_scopes(self, scopes): - scopes = scopes[0].split(",") - return all(scope in self._scopes for scope in scopes) + return True def check_response_type(self, response_type): allowed_response_types = [] diff --git a/fence/oidc/endpoints.py b/fence/oidc/endpoints.py index b0ccbcacd..254d9ef69 100644 --- a/fence/oidc/endpoints.py +++ b/fence/oidc/endpoints.py @@ -7,7 +7,7 @@ from fence.errors import BlacklistingError import fence.jwt.blacklist - +import jwt logger = get_logger(__name__) @@ -18,20 +18,20 @@ class RevocationEndpoint(authlib.oauth2.rfc7009.RevocationEndpoint): server should handle requests for token revocation. """ - def query_token(self, token, token_type_hint, client): + def query_token(self, token, token_type_hint): """ Look up a token. Since all tokens are JWT, just return the token. """ - return token + return JWTToken(token) - def revoke_token(self, token): + def revoke_token(self, token, request): """ Revoke a token. """ try: - fence.jwt.blacklist.blacklist_encoded_token(token) + fence.jwt.blacklist.blacklist_encoded_token(token.encoded_string) except BlacklistingError as err: logger.info( "Token provided for revocation is not valid. " @@ -109,3 +109,25 @@ def create_revocation_response(self): finally: body = {"error": message} if message != "" else {} return (status, body, headers) + + +class JWTToken(object): + def __init__(self, token): + self.encoded_string = token + self.client_id = jwt.decode( + token, algorithms=["RS256"], options={"verify_signature": False} + ).get("azp") + + def check_client(self, client): + """ + Check if token is issued by the same client + Expected function by Authlib + + Args: + client: oidc client + + Returns: + boolean value whether client_id matches + """ + + return self.client_id == client.client_id diff --git a/fence/oidc/grants/__init__.py b/fence/oidc/grants/__init__.py index f3740ba19..c6e372bd7 100644 --- a/fence/oidc/grants/__init__.py +++ b/fence/oidc/grants/__init__.py @@ -1,4 +1,4 @@ from fence.oidc.grants.implicit_grant import ImplicitGrant -from fence.oidc.grants.oidc_code_grant import OpenIDCodeGrant +from fence.oidc.grants.oidc_code_grant import AuthorizationCodeGrant from fence.oidc.grants.refresh_token_grant import RefreshTokenGrant from fence.oidc.grants.client_credentials_grant import ClientCredentialsGrant diff --git a/fence/oidc/grants/implicit_grant.py b/fence/oidc/grants/implicit_grant.py index e1532b926..784f412c9 100644 --- a/fence/oidc/grants/implicit_grant.py +++ b/fence/oidc/grants/implicit_grant.py @@ -14,7 +14,7 @@ def exists_nonce(self, nonce, request): return True return False - def create_authorization_response(self, grant_user): + def create_authorization_response(self, redirect_uri, grant_user): """ Overrides method from authlib---authlib has some peculiarities here such as trying to access ``token["scope"]`` from the token response which is not @@ -22,6 +22,9 @@ def create_authorization_response(self, grant_user): here: https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse + + 2024-04-19 + TODO: Re-evaluate this whether if it is still necessary. """ state = self.request.state if grant_user: @@ -46,7 +49,7 @@ def create_authorization_response(self, grant_user): # http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes return create_response_mode_response( - redirect_uri=self.redirect_uri, + redirect_uri=redirect_uri, params=params, response_mode=self.request.data.get( "response_mode", self.DEFAULT_RESPONSE_MODE diff --git a/fence/oidc/grants/oidc_code_grant.py b/fence/oidc/grants/oidc_code_grant.py index 771b6b59d..0514e68a5 100644 --- a/fence/oidc/grants/oidc_code_grant.py +++ b/fence/oidc/grants/oidc_code_grant.py @@ -1,27 +1,31 @@ from authlib.common.security import generate_token -from authlib.oidc.core import grants +from authlib.oauth2.rfc6749 import grants from authlib.oidc.core.errors import ( AccountSelectionRequiredError, ConsentRequiredError, LoginRequiredError, ) -from authlib.oauth2.rfc6749 import InvalidRequestError +from authlib.oauth2.rfc6749 import ( + InvalidRequestError, + UnauthorizedClientError, + InvalidGrantError, +) import flask from fence.utils import get_valid_expiration_from_request from fence.config import config from fence.models import AuthorizationCode, ClientAuthType, User +from cdislogging import get_logger + +logger = get_logger(__name__) -class OpenIDCodeGrant(grants.OpenIDCodeGrant): +class AuthorizationCodeGrant(grants.AuthorizationCodeGrant): TOKEN_ENDPOINT_AUTH_METHODS = [auth_type.value for auth_type in ClientAuthType] def __init__(self, *args, **kwargs): - super(OpenIDCodeGrant, self).__init__(*args, **kwargs) + super(AuthorizationCodeGrant, self).__init__(*args, **kwargs) # Override authlib validate_request_prompt with our own, to fix login prompt behavior - self._hooks["after_validate_consent_request"].discard( - grants.util.validate_request_prompt - ) self.register_hook( "after_validate_consent_request", self.validate_request_prompt ) @@ -60,12 +64,53 @@ def create_authorization_code(client, grant_user, request): return code.code + def save_authorization_code(self, code, request): + """Save authorization_code for later use. Must be implemented. + + Args: + code: authorization code string + request: HTTP request + + Returns: + authorization code string + """ + # requested lifetime (in seconds) for the refresh token + refresh_token_expires_in = get_valid_expiration_from_request( + expiry_param="refresh_token_expires_in", + max_limit=config["REFRESH_TOKEN_EXPIRES_IN"], + default=config["REFRESH_TOKEN_EXPIRES_IN"], + ) + + client = request.client + code = AuthorizationCode( + code=code, + client_id=client.client_id, + redirect_uri=request.redirect_uri, + scope=request.scope, + user_id=request.user.id, + nonce=request.data.get("nonce"), + refresh_token_expires_in=refresh_token_expires_in, + ) + + with flask.current_app.db.session as session: + session.add(code) + session.commit() + return code.code + def generate_token(self, *args, **kwargs): return self.server.generate_token(*args, **kwargs) def create_token_response(self): + """Generate Tokens + + Raises: + InvalidRequestError: if no user present in authorization code + + Returns: + HTTP status code, token, HTTP response header + """ client = self.request.client - authorization_code = self.request.credential + authorization_code = self.request.authorization_code user = self.authenticate_user(authorization_code) if not user: @@ -80,7 +125,7 @@ def create_token_response(self): self.GRANT_TYPE, user=user, scope=scope, - include_refresh_token=client.has_client_secret(), + include_refresh_token=bool(client.client_secret), nonce=nonce, refresh_token_expires_in=refresh_token_expires_in, ) @@ -92,7 +137,7 @@ def create_token_response(self): return 200, token, self.TOKEN_RESPONSE_HEADER @staticmethod - def parse_authorization_code(code, client): + def query_authorization_code(code, client): """ Search for an ``AuthorizationCode`` matching the given code string and client. @@ -142,7 +187,7 @@ def exists_nonce(self, nonce, request): return True return False - def validate_request_prompt(self, end_user): + def validate_request_prompt(self, end_user, redirect_uri): """ Override method in authlib to fix behavior with login prompt. """ @@ -175,3 +220,47 @@ def validate_request_prompt(self, end_user): self.prompt = prompt return self + + def validate_token_request(self): + """ + Validate token request by checking allowed grant type, + making sure authorization code is found, and redirect URI is valid + + Raises: + UnauthorizedClientError: if grant type is incorrect + InvalidRequestError: if authorization code is absent + InvalidGrantError: if authorization code is invalid + InvalidGrantError: if redirect_uri is invalid + """ + # authenticate the client if client authentication is included + logger.debug("Authenticating token client..") + client = self.authenticate_token_endpoint_client() + + logger.debug("Validate token request of %r", client) + if not client.check_grant_type(self.GRANT_TYPE): + raise UnauthorizedClientError( + f'The client is not authorized to use "grant_type={self.GRANT_TYPE}"' + ) + + code = self.request.data.get("code") + if code is None: + raise InvalidRequestError('Missing "code" in request.') + + # ensure that the authorization code was issued to the authenticated + # confidential client, or if the client is public, ensure that the + # code was issued to "client_id" in the request + authorization_code = self.query_authorization_code(code, client) + if not authorization_code: + raise InvalidGrantError('Invalid "code" in request.') + + # validate redirect_uri parameter + logger.debug("Validate token redirect_uri of %r", client) + redirect_uri = self.request.redirect_uri + original_redirect_uri = authorization_code.get_redirect_uri() + if original_redirect_uri and redirect_uri != original_redirect_uri: + raise InvalidGrantError('Invalid "redirect_uri" in request.') + + # save for create_token_response + self.request.client = client + self.request.authorization_code = authorization_code + self.execute_hook("after_validate_token_request") diff --git a/fence/oidc/grants/refresh_token_grant.py b/fence/oidc/grants/refresh_token_grant.py index 5c01a9a6a..b607bd9af 100644 --- a/fence/oidc/grants/refresh_token_grant.py +++ b/fence/oidc/grants/refresh_token_grant.py @@ -4,6 +4,7 @@ InvalidRequestError, InvalidScopeError, UnauthorizedClientError, + InvalidGrantError, ) from authlib.oauth2.rfc6749.grants import RefreshTokenGrant as AuthlibRefreshTokenGrant from authlib.oauth2.rfc6749.util import scope_to_list @@ -74,7 +75,7 @@ def validate_token_request(self): raise UnauthorizedClientError("invalid grant type") self.request.client = client self.authenticate_token_endpoint_client() - token = self._validate_request_token() + token = self._validate_request_token(client) self._validate_token_scope(token) self.request.credential = token @@ -141,7 +142,10 @@ def create_token_response(self): ##### end refresh token patch block ##### expires_in = credential["exp"] token = self.generate_token( - client, self.GRANT_TYPE, user=user, expires_in=expires_in, scope=scope + user=user, + scope=scope, + grant_type=self.GRANT_TYPE, + expires_in=expires_in, ) # replace the newly generated refresh token with the one provided @@ -154,14 +158,29 @@ def create_token_response(self): if self.GRANT_TYPE == "refresh_token": token["refresh_token"] = self.request.data.get("refresh_token", "") - # TODO - logger.info("") - self.request.user = user self.server.save_token(token, self.request) self.execute_hook("process_token", token=token) return 200, token, self.TOKEN_RESPONSE_HEADER + def _validate_request_token(self, client): + """ + OVERRIDES method from authlib. + + Why? Becuase our "token" is not a class with `check_client` method. + So we just need to treat it like a dictionary. + """ + refresh_token = self.request.data.get("refresh_token") + if refresh_token is None: + raise InvalidRequestError( + 'Missing "refresh_token" in request.', + ) + + token = self.authenticate_refresh_token(refresh_token) + if not token or not token["azp"] == client.get_client_id(): + raise InvalidGrantError() + return token + def _validate_token_scope(self, token): """ OVERRIDES method from authlib. diff --git a/fence/oidc/jwt_generator.py b/fence/oidc/jwt_generator.py index dfba556b6..2787add77 100644 --- a/fence/oidc/jwt_generator.py +++ b/fence/oidc/jwt_generator.py @@ -15,6 +15,7 @@ ) from fence.config import config +from fence.utils import validate_scopes def generate_token(client, grant_type, **kwargs): @@ -47,6 +48,10 @@ def generate_token(client, grant_type, **kwargs): claims (to avoid having to encode or decode the refresh token here) """ + # We need to validate scopes here because Authlib only check request scope against + # server's allowed_scopes + validate_scopes(kwargs["scope"], client) + if grant_type == "authorization_code" or grant_type == "refresh_token": return generate_token_response(client, grant_type, **kwargs) elif grant_type == "implicit": diff --git a/fence/oidc/oidc_server.py b/fence/oidc/oidc_server.py index 391e42d92..9b9aefad7 100644 --- a/fence/oidc/oidc_server.py +++ b/fence/oidc/oidc_server.py @@ -1,14 +1,25 @@ -from authlib.common.urls import urlparse, url_decode -from authlib.flask.oauth2 import AuthorizationServer +import flask + +from fence.oidc.errors import InvalidClientError +from fence.oidc.jwt_generator import generate_token + +from authlib.integrations.flask_oauth2 import AuthorizationServer from authlib.oauth2.rfc6749.authenticate_client import ( ClientAuthentication as AuthlibClientAuthentication, ) -from authlib.oauth2.rfc6749.errors import InvalidClientError as AuthlibClientError -import flask +from authlib.oauth2.rfc6749.errors import ( + InvalidClientError as AuthlibClientError, + OAuth2Error, + UnsupportedGrantTypeError, +) -from fence.oidc.errors import InvalidClientError -from fence.oidc.jwt_generator import generate_token +from fence import logger +from cdislogging import get_logger +from flask.wrappers import Request +from authlib.oauth2.rfc6749 import OAuth2Request + +logger = get_logger(__name__) class ClientAuthentication(AuthlibClientAuthentication): @@ -17,23 +28,30 @@ class ClientAuthentication(AuthlibClientAuthentication): in order to authenticate OAuth clients. """ - def authenticate(self, request, methods): + def authenticate(self, request, methods, endpoint): """ Override method from authlib """ - client = super(ClientAuthentication, self).authenticate(request, methods) + client = super(ClientAuthentication, self).authenticate( + request, methods, endpoint + ) + + logger.info("oidc_server.py clientAuthentioncation authenticate complete") # don't allow confidential clients to not use auth if client.is_confidential: m = list(methods) if "none" in m: m.remove("none") try: - client = super(ClientAuthentication, self).authenticate(request, m) + client = super(ClientAuthentication, self).authenticate( + request, m, endpoint + ) except AuthlibClientError: raise InvalidClientError( - "OAuth client failed to authenticate; client ID or secret is" + "Confidential OAuth client failed to authenticate; client ID or secret is" " missing or incorrect" ) + return client @@ -53,6 +71,57 @@ def init_app(self, app, query_client=None, save_token=None): self.save_token = save_token self.app = app self.generate_token = generate_token - self.init_jwt_config(app) if getattr(self, "query_client"): self.authenticate_client = ClientAuthentication(query_client) + + # 2023-09-29 + # Below code replaces authlib functions. It does the same thing as authlib 1.2.1 except it returns grant_scope from + # either args or forms. Authlib 1.2.1 forces grant_type to be part of post request body which isn't the current use case. + # https://github.com/lepture/authlib/blob/a6e89f8e6cf6f6bebd63dcdc2665b7d22cf0fde3/authlib/oauth2/rfc6749/requests.py#L59C10-L59C10 + def create_token_response(self, request=None): + """Validate token request and create token response. + + Args: + request: HTTP request instance + Returns: + HTTP response with token + """ + request = self.create_oauth2_request(request) + + try: + grant = self.get_token_grant(request) + except UnsupportedGrantTypeError as error: + return self.handle_error_response(request, error) + + logger.debug("Got grant succesfully") + + try: + grant.validate_token_request() + logger.debug("Token Request validated succesfully") + args = grant.create_token_response() + logger.debug("Token created succesfully") + return self.handle_response(*args) + except OAuth2Error as error: + return self.handle_error_response(request, error) + + def create_oauth2_request(self, request): + return FenceOAuth2Request(flask.request) + + +class FenceOAuth2Request(OAuth2Request): + def __init__(self, request: Request): + super().__init__(request.method, request.url, None, request.headers) + self._request = request + + @property + def args(self): + return self._request.args + + @property + def form(self): + return self._request.values + + # Get grant_type from either url or body + @property + def grant_type(self) -> str: + return self.data.get("grant_type") diff --git a/fence/oidc/server.py b/fence/oidc/server.py index 846fd4224..67d0a6089 100644 --- a/fence/oidc/server.py +++ b/fence/oidc/server.py @@ -9,7 +9,7 @@ from fence.oidc.client import authenticate_public_client, query_client from fence.oidc.endpoints import RevocationEndpoint from fence.oidc.grants import ( - OpenIDCodeGrant, + AuthorizationCodeGrant, ImplicitGrant, RefreshTokenGrant, ClientCredentialsGrant, @@ -18,7 +18,7 @@ server = OIDCServer(query_client=query_client, save_token=lambda *_: None) -server.register_grant(OpenIDCodeGrant) +server.register_grant(AuthorizationCodeGrant) server.register_grant(ImplicitGrant) server.register_grant(RefreshTokenGrant) server.register_grant(ClientCredentialsGrant) diff --git a/fence/resources/admin/admin_users.py b/fence/resources/admin/admin_users.py index f86f45178..373912c17 100644 --- a/fence/resources/admin/admin_users.py +++ b/fence/resources/admin/admin_users.py @@ -1,6 +1,6 @@ from cdislogging import get_logger -from cirrus import GoogleCloudManager -from cirrus.google_cloud.utils import get_proxy_group_name_for_user +from gen3cirrus import GoogleCloudManager +from gen3cirrus.google_cloud.utils import get_proxy_group_name_for_user from fence.config import config from fence.errors import NotFound, UserError, UnavailableError from fence.models import ( @@ -363,7 +363,7 @@ def delete_user(current_session, username): # and check if it exists in cirrus, in case Fence db just # didn't know about it. logger.debug( - "Could not find Google proxy group for this user in Fence db. Checking cirrus..." + "Could not find Google proxy group for this user in Fence db. Checking gen3cirrus..." ) pgname = get_proxy_group_name_for_user( user.id, user.username, prefix=config["GOOGLE_GROUP_PREFIX"] @@ -377,7 +377,7 @@ def delete_user(current_session, username): if not gpg_email: logger.info( - "Could not find Google proxy group for user in Fence db or in cirrus. " + "Could not find Google proxy group for user in Fence db or in gen3cirrus. " "Assuming Google not in use as IdP. Proceeding with Fence deletes." ) else: diff --git a/fence/resources/google/access_utils.py b/fence/resources/google/access_utils.py index be1c77978..212688845 100644 --- a/fence/resources/google/access_utils.py +++ b/fence/resources/google/access_utils.py @@ -8,10 +8,10 @@ from urllib.parse import unquote import traceback -from cirrus.google_cloud.iam import GooglePolicyMember -from cirrus.google_cloud.errors import GoogleAPIError -from cirrus.google_cloud.iam import GooglePolicy -from cirrus import GoogleCloudManager +from gen3cirrus.google_cloud.iam import GooglePolicyMember +from gen3cirrus.google_cloud.errors import GoogleAPIError +from gen3cirrus.google_cloud.iam import GooglePolicy +from gen3cirrus import GoogleCloudManager import fence from cdislogging import get_logger @@ -218,7 +218,7 @@ def get_google_project_valid_users_and_service_accounts( Will make call to Google API if membership is None Return: - List[cirrus.google_cloud.iam.GooglePolicyMember]: Members on the + List[gen3cirrus.google_cloud.iam.GooglePolicyMember]: Members on the google project Raises: diff --git a/fence/resources/google/utils.py b/fence/resources/google/utils.py index 74e43cb3c..ae1be0a94 100644 --- a/fence/resources/google/utils.py +++ b/fence/resources/google/utils.py @@ -9,9 +9,9 @@ from sqlalchemy import desc, func from cdislogging import get_logger -from cirrus import GoogleCloudManager -from cirrus.google_cloud.iam import GooglePolicyMember -from cirrus.google_cloud.utils import ( +from gen3cirrus import GoogleCloudManager +from gen3cirrus.google_cloud.iam import GooglePolicyMember +from gen3cirrus.google_cloud.utils import ( get_valid_service_account_id_for_client, get_valid_service_account_id_for_user, ) diff --git a/fence/resources/google/validity.py b/fence/resources/google/validity.py index 0cf372032..505b83b2e 100644 --- a/fence/resources/google/validity.py +++ b/fence/resources/google/validity.py @@ -27,7 +27,7 @@ is_user_member_of_google_project, is_user_member_of_all_google_projects, ) -from cirrus.google_cloud import GoogleCloudManager +from gen3cirrus.google_cloud import GoogleCloudManager from cdislogging import get_logger diff --git a/fence/resources/openid/idp_oauth2.py b/fence/resources/openid/idp_oauth2.py index 3c681b1e5..c2e497085 100644 --- a/fence/resources/openid/idp_oauth2.py +++ b/fence/resources/openid/idp_oauth2.py @@ -1,4 +1,4 @@ -from authlib.client import OAuth2Session +from authlib.integrations.requests_client import OAuth2Session from cached_property import cached_property from flask import current_app from jose import jwt diff --git a/fence/resources/storage/__init__.py b/fence/resources/storage/__init__.py index acb402739..4aeaad268 100644 --- a/fence/resources/storage/__init__.py +++ b/fence/resources/storage/__init__.py @@ -1,7 +1,7 @@ import copy from functools import wraps -from storageclient import get_client +from fence.resources.storage.storageclient import get_client from fence.models import ( CloudProvider, diff --git a/fence/resources/storage/storageclient/__init__.py b/fence/resources/storage/storageclient/__init__.py new file mode 100644 index 000000000..8418f8cf5 --- /dev/null +++ b/fence/resources/storage/storageclient/__init__.py @@ -0,0 +1,12 @@ +from fence.resources.storage.storageclient.cleversafe import CleversafeClient +from fence.resources.storage.storageclient.google import GoogleCloudStorageClient + + +def get_client(config=None, backend=None): + try: + clients = {"cleversafe": CleversafeClient, "google": GoogleCloudStorageClient} + return clients[backend](config) + except KeyError as ex: + raise NotImplementedError( + "The input storage is currently not supported!: {0}".format(ex) + ) diff --git a/fence/resources/storage/storageclient/base.py b/fence/resources/storage/storageclient/base.py new file mode 100644 index 000000000..d327be597 --- /dev/null +++ b/fence/resources/storage/storageclient/base.py @@ -0,0 +1,224 @@ +from abc import abstractmethod, abstractproperty, ABCMeta +from .errors import ClientSideError +import logging +from cdislogging import get_logger + + +def handle_request(fun): + """ + Exception treatment for the REST API calls + """ + + def wrapper(self, *args, **kwargs): + """ + We raise an exception when + the code on the client side fails + Server side errors are taken care of + through response codes + """ + try: + return fun(self, *args, **kwargs) + except Exception as req_exception: + self.logger.exception("internal error") + raise ClientSideError(str(req_exception)) + + return wrapper + + +class StorageClient(object, metaclass=ABCMeta): + """Abstract storage client class""" + + def __init__(self, cls_name): + self.logger = get_logger(cls_name) + self.logger.setLevel(logging.DEBUG) + + @abstractproperty + def provider(self): + """ + Name of the storage provider. eg: ceph + """ + msg = "Provider not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def get_user(self, username): + """ + Get a user + :returns: a User object if the user exists, else None + """ + msg = "get_user not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def delete_user(self, username): + """ + Delete a user + :returns: None + :raise: + :NotFound: the user is not found + """ + msg = "delete_user not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def create_user(self, username): + """ + Create a user + :returns: User object + """ + msg = "create_user not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def list_users(self): + """ + List users + :returns: a list of User objects + """ + msg = "list_users not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def get_or_create_user(self, username): + """ + Tries to retrieve a user. + If the user is not found, a new one + is created and returned + """ + msg = "get_or_create_user not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def create_keypair(self, username): + """ + Creates a keypair for the user, and + returns it + """ + msg = "create_keypair not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def delete_keypair(self, username, access_key): + """ + Deletes a keypair from the user and + doesn't return anything + """ + msg = "delete_keypair not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def add_bucket_acl(self, bucket, username, access=None): + """ + Tries to grant a user access to a bucket + """ + msg = "add_bucket_acl not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def has_bucket_access(self, bucket, user_id): + """ + Check if the user appears in the acl + : returns Bool + """ + msg = "has_bucket_access not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def list_buckets(self): + """ + Return a list of Bucket objects + : [bucket1, bucket2,...] + """ + msg = "list_buckets not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def delete_all_keypairs(self, user): + """ + Remove all the keys from a user + : returns None + """ + msg = "delete_all_keypairs not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def get_bucket(self, bucket): + """ + Return a bucket from the storage + """ + msg = "get_bucket not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def get_or_create_bucket(self, bucket, access_key=None, secret_key=None): + """ + Tries to retrieve a bucket and if fit fails, + creates and returns one + """ + msg = "get_or_create_bucket not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def get_bucket(self, bucket, access_key=None, secret_key=None): + """ + Tries to retrieve a bucket and if fit fails, + creates and returns one + """ + msg = "get_bucket not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def edit_bucket_template(self, template_id, **kwargs): + """ + Change the parameters for the template used to create + the buckets + """ + msg = "edit_bucket_template not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def update_bucket_acl(self, bucket, user_list): + """ + Add acl's for the list of users + """ + msg = "update_bucket_acl not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def set_bucket_quota(self, bucket, quota_unit, quota): + """ + Set quota for the entire bucket + """ + msg = "set_bucket_quota not implemented" + raise NotImplementedError(msg) + + @abstractmethod + def delete_bucket_acl(self, bucket, user): + """ + Set quota for the entire bucket + """ + msg = "delete_bucket_acl not implemented" + raise NotImplementedError(msg) + + +class User(object): + def __init__(self, username): + """ + - permissions {'bucketname': 'PERMISSION'} + - keys [{'access_key': abc,'secret_key': 'def'}] + """ + self.username = username + self.permissions = {} + self.keys = [] + self.id = None + + +class Bucket(object): + def __init__(self, name, bucket_id, quota): + """ + Simple bucket representation + Quota is in TiBs or such units + """ + self.name = name + self.id = bucket_id + self.quota = quota diff --git a/fence/resources/storage/storageclient/cleversafe.py b/fence/resources/storage/storageclient/cleversafe.py new file mode 100644 index 000000000..2517b13f9 --- /dev/null +++ b/fence/resources/storage/storageclient/cleversafe.py @@ -0,0 +1,531 @@ +""" +Connection manager for the Cleversafe storage system +Since it is compatible with S3, we will be using boto. +""" +from boto import connect_s3 +from boto.s3 import connection +from boto.exception import S3ResponseError +from boto.s3.acl import Grant +import requests +from urllib.parse import urlencode +import json +from .base import StorageClient, User, Bucket, handle_request +from .errors import RequestError, NotFoundError + +from fence.config import config + + +class CleversafeClient(StorageClient): + """ + Connection manager for Cleversafe. + Isolates differences from other connectors + """ + + def __init__(self, config): + """ + Creation of the manager. Since it is only s3 compatible + we need to specify the endpoint in the config + """ + super(CleversafeClient, self).__init__(__name__) + self._config = config + self._host = config["host"] + self._public_host = config["public_host"] + self._access_key = config["aws_access_key_id"] + self._secret_key = config["aws_secret_access_key"] + self._username = config["username"] + self._password = config["password"] + self._permissions_order = { + "read-storage": 1, + "write-storage": 2, + "admin-storage": 3, + "disabled": 0, + } + self._permissions_value = ["disabled", "readOnly", "readWrite", "owner"] + self._auth = requests.auth.HTTPBasicAuth(self._username, self._password) + self._conn = connect_s3( + aws_access_key_id=self._access_key, + aws_secret_access_key=self._secret_key, + host=self._public_host, + calling_format=connection.OrdinaryCallingFormat(), + ) + self._bucket_name_id_table = {} + self._update_bucket_name_id_table() + self._user_name_id_table = {} + self._user_id_name_table = {} + self._update_user_name_id_table() + + def _update_user_name_id_table(self): + """ + Update the name-id translation table for users + """ + response = self._request("GET", "listAccounts.adm") + if response.status_code == 200: + jsn = json.loads(response.text) + self._user_name_id_table = {} + for user in jsn["responseData"]["accounts"]: + self._user_name_id_table[user["name"]] = user["id"] + self._user_id_name_table[user["id"]] = user["name"] + self.logger.debug(self._user_name_id_table) + self.logger.debug(self._user_id_name_table) + else: + msg = "List users failed on update cache with code {0}" + self.logger.error(msg.format(response.status_code)) + raise RequestError(response.text, response.status_code) + + def _update_bucket_name_id_table(self): + """ + Update the name-id translation table for buckets + """ + response = self._request("GET", "listVaults.adm") + if response.status_code == 200: + jsn = json.loads(response.text) + self._bucket_name_id_table = {} + for user in jsn["responseData"]["vaults"]: + self._bucket_name_id_table[user["name"]] = user["id"] + self.logger.debug(self._bucket_name_id_table) + else: + msg = "List vaults failed on update cache with code {0}" + self.logger.error(msg.format(response.status_code)) + raise RequestError(response.text, response.status_code) + + def _get_bucket_id(self, name): + """ + Tries to return the id from the table + If the cache misses, it updates it and + tries again + TODO OPTIMIZATION get the user information + from the update itself + """ + try: + return self._bucket_name_id_table[name] + except KeyError: + self._update_bucket_name_id_table() + return self._bucket_name_id_table[name] + + def _get_user_id(self, name): + """ + Tries to return the id from the table + If the cache misses, it updates it and + tries again + """ + try: + return self._user_name_id_table[name] + except KeyError: + self._update_user_name_id_table() + return self._user_name_id_table[name] + + def _get_user_by_id(self, uid): + """ + Fetches the user by id from the REST API + """ + response = self._request("GET", "viewSystem.adm", itemType="account", id=uid) + if response.status_code == 200: + user = json.loads(response.text) + try: + return self._populate_user(user["responseData"]["accounts"][0]) + except: + # Request OK but User not found + return None + else: + self.logger.error( + "get_user failed with code: {code}".format(code=response.status_code) + ) + raise RequestError(response.text, response.status_code) + + def _populate_user(self, data): + """ + Populates a new user with the data provided + in a jsonreponse + """ + try: + new_user = User(data["name"]) + new_user.id = data["id"] + for key in data["accessKeys"]: + new_key = { + "access_key": key["accessKeyId"], + "secret_key": key["secretAccessKey"], + } + new_user.keys.append(new_key) + vault_roles = [] + for role in data["roles"]: + if role["role"] == "vaultUser": + vault_roles = role["vaultPermissions"] + for vault_permission in vault_roles: + vault_response = self._get_bucket_by_id(vault_permission["vault"]) + vault = json.loads(vault_response.text) + new_user.permissions[ + vault["responseData"]["vaults"][0]["name"] + ] = vault_permission["permission"] + return new_user + except KeyError as key_e: + msg = "Failed to parse the user data. Check user fields inside the accounts section" + self.logger.error(msg) + raise RequestError(str(key_e), "200") + + def _get_bucket_by_id(self, vid): + """ + Get bucket by id + """ + response = self._request("GET", "viewSystem.adm", itemType="vault", id=vid) + if response.status_code == 200: + return response + else: + msg = "Get bucket by id failed with code: {0}" + self.logger.error(msg.format(response.status_code)) + raise RequestError(response.text, response.status_code) + + @handle_request + def _request(self, method, operation, payload=None, **kwargs): + """ + Compose the request and send it + """ + base_url = "https://{host}/manager/api/json/1.0/{oper}".format( + host=self._host, oper=operation + ) + url = base_url + "?" + urlencode(dict(**kwargs)) + return requests.request( + method, + url, + auth=self._auth, + data=payload, + verify=config["VERIFY_CLEVERSAFE_CERT"], + ) # self-signed certificate + + @property + def provider(self): + """ + Returns the type of storage + """ + return "Cleversafe" + + def list_users(self): + """ + Returns a list with all the users, in User objects + """ + response = self._request("GET", "listAccounts.adm") + if response.status_code == 200: + jsn = json.loads(response.text) + user_list = [] + for user in jsn["responseData"]["accounts"]: + new_user = self._populate_user(user) + user_list.append(new_user) + return user_list + else: + msg = "List buckets failed with code {0}" + self.logger.error(msg.format(response.status_code)) + raise RequestError(response.text, response.status_code) + + def has_bucket_access(self, bucket, username): + """ + Find if a user is in the grants list of the acl for + a certain bucket. + Please keep in mind that buckets must be all lowercase + """ + vault_id = self._get_bucket_id(bucket) + vault = json.loads(self._get_bucket_by_id(vault_id).text) + user_id = self._get_user_id(username) + for permission in vault["responseData"]["vaults"][0]["accessPermissions"]: + if permission["principal"]["id"] == user_id: + return True + return False + + def get_user(self, name): + """ + Gets the information from the user including + but not limited to: + - username + - name + - roles + - permissions + - access_keys + - emailxs + """ + try: + uid = self._get_user_id(name) + except KeyError: + return None + return self._get_user_by_id(uid) + + def list_buckets(self): + """ + Lists all the vaults(buckets) and their information + """ + response = self._request("GET", "listVaults.adm") + if response.status_code == 200: + buckets = json.loads(response.text) + bucket_list = [] + for buck in buckets["responseData"]["vaults"]: + new_bucket = Bucket(buck["name"], buck["id"], buck["hardQuota"]) + bucket_list.append(new_bucket) + return bucket_list + else: + self.logger.error( + "List buckets failed with code: {code}".format( + code=response.status_code + ) + ) + raise RequestError(response.text, response.status_code) + + def create_user(self, name): + """ + Creates a user + TODO Input sanitazion for parameters + """ + data = {"name": name, "usingPassword": "false"} + response = self._request("POST", "createAccount.adm", payload=data) + if response.status_code == 200: + parsed_reply = json.loads(response.text) + user_id = parsed_reply["responseData"]["id"] + self._update_user_name_id_table() + return self._get_user_by_id(user_id) + else: + self.logger.error( + "User creation failed with code: {0}".format(response.status_code) + ) + raise RequestError(response.text, response.status_code) + + def delete_user(self, name): + """ + Eliminate a user account + Requires the password from the account requesting the deletion + """ + uid = self._get_user_id(name) + data = {"id": uid, "password": self._config["password"]} + response = self._request("POST", "deleteAccount.adm", payload=data) + if response.status_code == 200: + self._update_user_name_id_table() + return None + else: + self.logger.error( + "Delete user failed with code: {0}".format(response.status_code) + ) + raise RequestError(response.text, response.status_code) + + def delete_keypair(self, name, access_key): + """ + Remove the give key/secret that match the key id + """ + uid = self._get_user_id(name) + data = {"id": uid, "accessKeyId": access_key, "action": "remove"} + response = self._request("POST", "editAccountAccessKey.adm", payload=data) + if response.status_code == 200: + return None + else: + self.logger.error( + "Delete keypair failed with code: {0}".format(response.status_code) + ) + raise RequestError(response.text, response.status_code) + + def delete_all_keypairs(self, name): + """ + Remove all keys from a give user + TODO Make this robust against possible errors so most of the keys are deleted + or retried + """ + user = self.get_user(name) + exception = False + responses_list = [] + responses_codes = [] + for key in user.keys: + try: + self.delete_keypair(user.username, key["access_key"]) + except RequestError as exce: + exception = True + msg = "Remove all keys failed for one key" + self.logger.error(msg.format(exce.code)) + responses_list.append(str(exce)) + responses_codes.append(exce.code) + if exception: + raise RequestError(responses_list, responses_codes) + else: + return None + + def create_keypair(self, name): + """ + Add a new key/secret pair + """ + uid = self._get_user_id(name) + data = {"id": uid, "action": "add"} + response = self._request("POST", "editAccountAccessKey.adm", payload=data) + if response.status_code == 200: + jsn = json.loads(response.text) + keypair = { + "access_key": jsn["responseData"]["accessKeyId"], + "secret_key": jsn["responseData"]["secretAccessKey"], + } + return keypair + else: + msg = "Create keypair failed with error code: {0}" + self.logger.error(msg.format(response.status_code)) + raise RequestError(response.text, response.status_code) + + def get_bucket(self, bucket): + """ + Retrieves the information from the bucket matching the name + """ + try: + bucket_id = self._get_bucket_id(bucket) + """at this point we have all we need for the initial + Bucket object, but for coherence, we keep this last call. + Feel free to get more information from response.text""" + response = self._get_bucket_by_id(bucket_id) + vault = json.loads(response.text) + return Bucket( + bucket, bucket_id, vault["responseData"]["vaults"][0]["hardQuota"] + ) + except KeyError as exce: + self.logger.error("Get bucket not found on cache") + raise RequestError(str(exce), "NA") + except RequestError as exce: + self.logger.error("Get bucket failed retrieving bucket info") + raise exce + + def get_or_create_user(self, name): + """ + Tries to get a user and if it doesn't exist, creates a new one + """ + user = self.get_user(name) + if user != None: + return user + else: + return self.create_user(name) + + def get_or_create_bucket(self, bucket_name, access_key=None, secret_key=None): + """ + Tries to retrieve a bucket and if it doesn't exist, creates a new one + """ + bucket = self.get_bucket(bucket_name) + if bucket != None: + return bucket + else: + if not access_key: + access_key = self._access_key + if not secret_key: + secret_key = self._secret_key + return self.create_bucket(bucket_name, access_key, secret_key) + + def create_bucket(self, bucket_name, access_key=None, secret_key=None): + """ + Requires a default template created on cleversafe + """ + if not access_key: + access_key = self._access_key + if not secret_key: + secret_key = self._secret_key + creds = {"host": self._public_host} + creds["aws_access_key_id"] = access_key + creds["aws_secret_access_key"] = secret_key + conn = connect_s3(calling_format=connection.OrdinaryCallingFormat(), **creds) + try: + bucket = conn.create_bucket(bucket_name) + self._update_bucket_name_id_table() + return bucket + except S3ResponseError as exce: + msg = "Create bucket failed with error code: {0}" + self.logger.error(msg.format(exce.error_code)) + raise RequestError(str(exce), exce.error_code) + + def edit_bucket_template(self, default_template_id, **kwargs): + """ + Change the desired parameters of the default template + This will affect every new bucket creation + The idea is to have only one template, the default one, and + modify it accordingly + """ + data = kwargs + data["id"] = default_template_id + response = self._request("POST", "editVaultTemplate.adm", payload=data) + if response.status_code == 200: + return response + else: + msg = "Edit bucket template failed with code: {0}" + self.logger.error(msg.format(response.status_code)) + raise RequestError(response.text, response.status_code) + + def update_bucket_acl(self, bucket, new_grants): + """ + Get an acl object and add the missing credentials + to the one retrieved from the target bucket + new_grants contains a list of users and permissions + [('user1', ['read-storage', 'write-storage']),...] + """ + user_id_list = [] + for user in new_grants: + user_id_list.append(self._get_user_id(user[0])) + bucket_id = self._get_bucket_id(bucket) + response = self._get_bucket_by_id(bucket_id) + vault = json.loads(response.text)["responseData"]["vaults"][0] + disable = [] + for permission in vault["accessPermissions"]: + uid = permission["principal"]["id"] + permit_type = permission["permission"] + if uid not in user_id_list or permit_type != "owner": + disable.append((self._user_id_name_table[uid], ["disabled"])) + for user in disable: + self.add_bucket_acl(bucket, user[0], user[1]) + for user in new_grants: + self.add_bucket_acl(bucket, user[0], user[1]) + + def set_bucket_quota(self, bucket, quota_unit, quota): + """ + Set qouta for the entire bucket/vault + """ + bid = self._get_bucket_id(bucket) + data = {"hardQuotaSize": quota, "hardQuotaUnit": quota_unit, "id": bid} + response = self._request("POST", "editVault.adm", payload=data) + if response.status_code == 200: + return response + else: + msg = "Set bucket quota failed with code: {0}" + self.logger.error(msg.format(response.status_code)) + raise RequestError(response.text, response.status_code) + + def add_bucket_acl(self, bucket, username, access=[]): + """ + Add permissions to a user on the bucket ACL + """ + try: + bucket_param = "vaultUserPermissions[{0}]".format( + self._get_bucket_id(bucket) + ) + except KeyError: + msg = "Bucket {0} wasn't found on the database" + self.logger.error(msg.format(bucket)) + raise NotFoundError(msg.format(bucket)) + try: + access_lvl = max(self._permissions_order[role] for role in access) + data = { + "id": self._get_user_id(username), + bucket_param: self._permissions_value[access_lvl], + } + if access_lvl == 3: + data["rolesMap[vaultProvisioner]"] = "true" + except KeyError: + msg = "User {0} wasn't found on the database" + self.logger.error(msg.format(username)) + raise NotFoundError(msg.format(username)) + response = self._request("POST", "editAccount.adm", payload=data) + if response.status_code != 200: + msg = "Error trying to change buket permissions for user {0}" + self.logger.error(msg.format(username)) + raise RequestError(msg.format(username), response.status_code) + + def delete_bucket(self, bucket_name): + """ + Delete a bucket + """ + bucket_id = self._get_bucket_id(bucket_name) + data = {"id": bucket_id, "password": self._password} + response = self._request("POST", "deleteVault.adm", payload=data) + self._update_bucket_name_id_table() + if response.status_code != 200: + msg = "Error trying to delete vault {bucket}" + self.logger.error(msg.format(bucket_name)) + raise RequestError(msg.format(bucket_name), response.status_code) + + def delete_bucket_acl(self, bucket, username): + """ + Remove permission from a bucket + """ + self.add_bucket_acl(bucket, username, ["disabled"]) + return None diff --git a/fence/resources/storage/storageclient/errors.py b/fence/resources/storage/storageclient/errors.py new file mode 100644 index 000000000..8f9eddb78 --- /dev/null +++ b/fence/resources/storage/storageclient/errors.py @@ -0,0 +1,14 @@ +class RequestError(Exception): + def __init__(self, message, code): + self.message = message + self.code = code + + +class NotFoundError(RequestError): + def __init__(self, message): + super().__init__(message, 404) + + +class ClientSideError(RequestError): + def __init__(self, message): + super().__init__(message, 400) diff --git a/fence/resources/storage/storageclient/google.py b/fence/resources/storage/storageclient/google.py new file mode 100644 index 000000000..0733f9a09 --- /dev/null +++ b/fence/resources/storage/storageclient/google.py @@ -0,0 +1,217 @@ +from fence.resources.storage.storageclient.base import StorageClient +from fence.resources.storage.storageclient.errors import RequestError +from gen3cirrus import GoogleCloudManager + + +class UserProxy(object): + def __init__(self, username): + self.username = username + + +class GoogleCloudStorageClient(StorageClient): + def __init__(self, config): + super(GoogleCloudStorageClient, self).__init__(__name__) + self._config = config + self.google_project_id = config.get("google_project_id") + + @property + def provider(self): + """ + Returns the type of storage + """ + return "GoogleCloudStorage" + + def get_user(self, username): + """ + Get a user + + Args: + username (str): An email address representing a User's Google + Proxy Group (e.g. a single Google Group to hold a single + user's diff identities). + + Returns: + UserProxy: a UserProxy object if the user exists, else None + """ + user_proxy = None + + with GoogleCloudManager(project_id=self.google_project_id) as g_mgr: + user_proxy_response = g_mgr.get_group(username) + if user_proxy_response.get("email"): + user_proxy = UserProxy(username=user_proxy_response.get("email")) + + return user_proxy + + def delete_user(self, username): + """ + Delete a user + :returns: None + :raise: + :NotFound: the user is not found + """ + msg = "delete_user not implemented" + raise NotImplementedError(msg) + + def create_user(self, username): + """ + Create a user + :returns: User object + """ + msg = "create_user not implemented" + raise NotImplementedError(msg) + + def list_users(self): + """ + List users + :returns: a list of User objects + """ + msg = "list_users not implemented" + raise NotImplementedError(msg) + + def get_or_create_user(self, username): + """ + Tries to retrieve a user. + + WARNING: If the user is not found, this DOES NOT CREATE ONE. + + Google architecture requires that a separate process populate + a proxy Google group per user. If it doesn't exist, we can't create it + here. + """ + user_proxy = self.get_user(username) + if not user_proxy: + raise Exception( + "Unable to determine User's Google Proxy group. Cannot create " + "here. Another process should create proxy groups for " + "new users. Username provided: {}".format(username) + ) + + return user_proxy + + def create_keypair(self, username): + """ + Creates a keypair for the user, and + returns it + """ + msg = "create_keypair not implemented" + raise NotImplementedError(msg) + + def delete_keypair(self, username, access_key): + """ + Deletes a keypair from the user and + doesn't return anything + """ + msg = "delete_keypair not implemented" + raise NotImplementedError(msg) + + def add_bucket_acl(self, bucket, username, access=None): + """ + Tries to grant a user access to a bucket + + Args: + bucket (str): Google Bucket Access Group email address. This should + be the address of a Google Group that has read access on a + single bucket. Access is controlled by adding members to this + group. + username (str): An email address of a member to add to the Google + Bucket Access Group. + access (str): IGNORED. For Google buckets, the Google Bucket Access + Group is given access to the bucket through Google's + IAM, so you cannot selectively choose permissions. Once you're + added, you have the access that was set up for that group + in Google IAM. + """ + response = None + with GoogleCloudManager(project_id=self.google_project_id) as g_mgr: + try: + response = g_mgr.add_member_to_group( + member_email=username, group_id=bucket + ) + except Exception as exc: + raise RequestError("Google API Error: {}".format(exc), code=400) + + return response + + def has_bucket_access(self, bucket, user_id): + """ + Check if the user appears in the acl + : returns Bool + """ + msg = "has_bucket_access not implemented" + raise NotImplementedError(msg) + + def list_buckets(self): + """ + Return a list of Bucket objects + : [bucket1, bucket2,...] + """ + msg = "list_buckets not implemented" + raise NotImplementedError(msg) + + def delete_all_keypairs(self, user): + """ + Remove all the keys from a user + : returns None + """ + msg = "delete_all_keypairs not implemented" + raise NotImplementedError(msg) + + def get_bucket(self, bucket): + """ + Return a bucket from the storage + """ + msg = "get_bucket not implemented" + raise NotImplementedError(msg) + + def get_or_create_bucket(self, bucket, access_key=None, secret_key=None): + """ + Tries to retrieve a bucket and if fit fails, + creates and returns one + """ + msg = "get_or_create_bucket not implemented" + raise NotImplementedError(msg) + + def edit_bucket_template(self, template_id, **kwargs): + """ + Change the parameters for the template used to create + the buckets + """ + msg = "edit_bucket_template not implemented" + raise NotImplementedError(msg) + + def update_bucket_acl(self, bucket, user_list): + """ + Add acl's for the list of users + """ + msg = "update_bucket_acl not implemented" + raise NotImplementedError(msg) + + def set_bucket_quota(self, bucket, quota_unit, quota): + """ + Set quota for the entire bucket + """ + msg = "set_bucket_quota not implemented" + raise NotImplementedError(msg) + + def delete_bucket_acl(self, bucket, user): + """ + Set quota for the entire bucket + + Args: + bucket (str): Google Bucket Access Group email address. This should + be the address of a Google Group that has read access on a + single bucket. Access is controlled by adding members to this + group. + user (str): An email address of a member to add to the Google + Bucket Access Group. + """ + response = None + with GoogleCloudManager(project_id=self.google_project_id) as g_mgr: + try: + response = g_mgr.remove_member_from_group( + member_email=user, group_id=bucket + ) + except Exception as exc: + raise RequestError("Google API Error: {}".format(exc), code=400) + + return response diff --git a/fence/resources/user/user_session.py b/fence/resources/user/user_session.py index 326c84860..fc061a74c 100644 --- a/fence/resources/user/user_session.py +++ b/fence/resources/user/user_session.py @@ -190,8 +190,8 @@ def save_session(self, app, session, response): token = session.get_updated_token(app) if token: response.set_cookie( - app.config["SESSION_COOKIE_NAME"], - token, + key=app.config["SESSION_COOKIE_NAME"], + value=token, expires=self.get_expiration_time(app, session), httponly=True, domain=domain, @@ -210,7 +210,7 @@ def save_session(self, app, session, response): # okay if user is hitting with just an access_token if user_sess_id != "" and not user: response.set_cookie( - config["ACCESS_TOKEN_COOKIE_NAME"], + key=config["ACCESS_TOKEN_COOKIE_NAME"], expires=0, httponly=True, domain=domain, @@ -221,7 +221,7 @@ def save_session(self, app, session, response): # clear access token if not elif user_sess_id != "" and user.id != user_sess_id: response.set_cookie( - config["ACCESS_TOKEN_COOKIE_NAME"], + key=config["ACCESS_TOKEN_COOKIE_NAME"], expires=0, httponly=True, domain=domain, @@ -250,14 +250,14 @@ def save_session(self, app, session, response): # expiration it just won't be stored in the cookie # anymore response.set_cookie( - app.config["SESSION_COOKIE_NAME"], + key=app.config["SESSION_COOKIE_NAME"], expires=0, httponly=True, domain=domain, secure=secure, ) response.set_cookie( - config["ACCESS_TOKEN_COOKIE_NAME"], + key=config["ACCESS_TOKEN_COOKIE_NAME"], expires=0, httponly=True, domain=domain, @@ -337,8 +337,8 @@ def _create_access_token_cookie(app, session, response, user): domain = app.session_interface.get_cookie_domain(app) response.set_cookie( - config["ACCESS_TOKEN_COOKIE_NAME"], - access_token, + key=config["ACCESS_TOKEN_COOKIE_NAME"], + value=access_token, expires=expiration, httponly=True, domain=domain, diff --git a/fence/scripting/fence_create.py b/fence/scripting/fence_create.py index 91bd5d81e..a4b15aff8 100644 --- a/fence/scripting/fence_create.py +++ b/fence/scripting/fence_create.py @@ -9,9 +9,9 @@ import asyncio from alembic.config import main as alembic_main -from cirrus import GoogleCloudManager -from cirrus.google_cloud.errors import GoogleAuthError -from cirrus.config import config as cirrus_config +from gen3cirrus import GoogleCloudManager +from gen3cirrus.google_cloud.errors import GoogleAuthError +from gen3cirrus.config import config as cirrus_config from cdislogging import get_logger from sqlalchemy import func from userdatamodel.models import ( @@ -62,7 +62,7 @@ generate_client_credentials, get_SQLAlchemyDriver, ) - +from sqlalchemy.orm.attributes import flag_modified from gen3authz.client.arborist.client import ArboristClient logger = get_logger(__name__) @@ -100,15 +100,19 @@ def modify_client_action( if not clients: raise Exception("client {} does not exist".format(client_name)) for client in clients: + metadata = client.client_metadata if urls: if append: - client.redirect_uris += urls + metadata["redirect_uris"] += urls logger.info("Adding {} to urls".format(urls)) else: - client.redirect_uris = urls + if isinstance(urls, list): + metadata["redirect_uris"] = urls + else: + metadata["redirect_uris"] = [urls] logger.info("Changing urls to {}".format(urls)) if delete_urls: - client.redirect_uris = [] + metadata["redirect_uris"] = [] logger.info("Deleting urls") if set_auto_approve: client.auto_approve = True @@ -124,19 +128,21 @@ def modify_client_action( logger.info("Updating description to {}".format(description)) if allowed_scopes: if append: - new_scopes = client._allowed_scopes.split() + allowed_scopes - client._allowed_scopes = " ".join(new_scopes) + new_scopes = client.scope.split() + allowed_scopes + metadata["scope"] = " ".join(new_scopes) logger.info("Adding {} to allowed_scopes".format(allowed_scopes)) else: - client._allowed_scopes = " ".join(allowed_scopes) + metadata["scope"] = " ".join(allowed_scopes) logger.info("Updating allowed_scopes to {}".format(allowed_scopes)) if expires_in: client.expires_at = get_client_expires_at( - expires_in=expires_in, grant_types=client.grant_type + expires_in=expires_in, grant_types=client.grant_types ) + # Call setter on Json object to persist changes if any + client.set_client_metadata(metadata) s.commit() - if arborist is not None and policies: - arborist.update_client(client.client_id, policies) + if arborist is not None and policies: + arborist.update_client(client.client_id, policies) def create_client_action( @@ -210,10 +216,10 @@ def delete_expired_clients_action(DB, slack_webhook=None, warning_days=None): # to delete pass - def split_uris(uris): - if not uris: + def format_uris(uris): + if not uris or uris == [None]: return uris - return uris.split("\n") + return " ".join(uris) now = datetime.now().timestamp() driver = get_SQLAlchemyDriver(DB) @@ -229,7 +235,7 @@ def split_uris(uris): for client in clients: expired_messages.append( - f"Client '{client.name}' (ID '{client.client_id}') expired at {datetime.fromtimestamp(client.expires_at)} UTC. Redirect URIs: {split_uris(client.redirect_uri)})" + f"Client '{client.name}' (ID '{client.client_id}') expired at {datetime.fromtimestamp(client.expires_at)} UTC. Redirect URIs: {format_uris(client.redirect_uris)})" ) _remove_client_service_accounts(current_session, client) current_session.delete(client) @@ -251,7 +257,7 @@ def split_uris(uris): expiring_messages = ["Some OIDC clients are expiring soon!"] expiring_messages.extend( [ - f"Client '{client.name}' (ID '{client.client_id}') expires at {datetime.fromtimestamp(client.expires_at)} UTC. Redirect URIs: {split_uris(client.redirect_uri)}" + f"Client '{client.name}' (ID '{client.client_id}') expires at {datetime.fromtimestamp(client.expires_at)} UTC. Redirect URIs: {format_uris(client.redirect_uris)}" for client in expiring_clients ] ) @@ -312,7 +318,7 @@ def rotate_client_action(DB, client_name, expires_in=None): # the rest is identical to the client being rotated user=client.user, redirect_uris=client.redirect_uris, - _allowed_scopes=client._allowed_scopes, + allowed_scopes=client.scope, description=client.description, name=client.name, auto_approve=client.auto_approve, @@ -1107,7 +1113,7 @@ def _verify_google_service_account_member(session, access_group, member): class JWTCreator(object): required_kwargs = ["kid", "private_key", "username", "scopes"] - all_kwargs = required_kwargs + ["expires_in"] + all_kwargs = required_kwargs + ["expires_in", "client_id"] default_expiration = 3600 @@ -1121,6 +1127,7 @@ def __init__(self, db, base_url, **kwargs): self.private_key = None self.username = None self.scopes = None + self.client_id = None for required_kwarg in self.required_kwargs: if required_kwarg not in kwargs: @@ -1181,6 +1188,10 @@ def create_refresh_token(self): raise EnvironmentError( "no user found with given username: " + self.username ) + if not self.client_id: + raise EnvironmentError( + "no client id is provided. Required for creating refresh token" + ) jwt_result = generate_signed_refresh_token( self.kid, self.private_key, @@ -1188,6 +1199,7 @@ def create_refresh_token(self): self.expires_in, self.scopes, iss=self.base_url, + client_id=self.client_id, ) current_session.add( diff --git a/fence/scripting/google_monitor.py b/fence/scripting/google_monitor.py index e4752212e..ad232519d 100644 --- a/fence/scripting/google_monitor.py +++ b/fence/scripting/google_monitor.py @@ -7,9 +7,9 @@ """ import traceback -from cirrus.google_cloud.iam import GooglePolicyMember -from cirrus import GoogleCloudManager -from cirrus.google_cloud.errors import GoogleAPIError +from gen3cirrus.google_cloud.iam import GooglePolicyMember +from gen3cirrus import GoogleCloudManager +from gen3cirrus.google_cloud.errors import GoogleAPIError from cdislogging import get_logger @@ -357,7 +357,6 @@ def _get_invalid_sa_project_removal_reasons(google_project_validity): def _get_access_removal_reasons(google_project_validity): - removal_reasons = {} if google_project_validity is None: @@ -537,7 +536,6 @@ def _get_users_without_access(db, auth_ids, user_emails, check_linking): no_access = {} for user_email in user_emails: - user = get_user_by_email(user_email, db) or get_user_by_linked_email( user_email, db ) @@ -588,7 +586,6 @@ def _get_users_without_access(db, auth_ids, user_emails, check_linking): def email_user_without_access(user_email, projects, google_project_id): - """ Send email to user, indicating no access to given projects @@ -618,7 +615,6 @@ def email_user_without_access(user_email, projects, google_project_id): def email_users_without_access( db, auth_ids, user_emails, check_linking, google_project_id ): - """ Build list of users without acess and send emails. diff --git a/fence/sync/sync_users.py b/fence/sync/sync_users.py index 37d535832..4320e58ab 100644 --- a/fence/sync/sync_users.py +++ b/fence/sync/sync_users.py @@ -1,3 +1,4 @@ +import backoff import glob import jwt import os @@ -42,7 +43,7 @@ from fence.resources.google.access_utils import GoogleUpdateException from fence.sync import utils from fence.sync.passport_sync.ras_sync import RASVisa -from fence.utils import get_SQLAlchemyDriver +from fence.utils import get_SQLAlchemyDriver, DEFAULT_BACKOFF_SETTINGS def _format_policy_id(path, privilege): @@ -429,13 +430,17 @@ def _get_from_sftp_with_proxy(self, server, path): parameters.get("port", "unknown"), ) ) - client.connect(**parameters) + self._connect_with_ssh(ssh_client=client, parameters=parameters) with client.open_sftp() as sftp: download_dir(sftp, "./", path) if proxy: proxy.close() + @backoff.on_exception(backoff.expo, Exception, **DEFAULT_BACKOFF_SETTINGS) + def _connect_with_ssh(self, ssh_client, parameters): + ssh_client.connect(**parameters) + def _get_from_ftp_with_proxy(self, server, path): """ Download data from ftp sever to a local dir @@ -1541,7 +1546,7 @@ def _download(self, dbgap_config): return dbgap_files except Exception as e: self.logger.error(e) - exit(1) + raise def _sync(self, sess): """ @@ -1868,13 +1873,17 @@ def _update_arborist(self, session, user_yaml): return True - def _revoke_all_policies_preserve_mfa(self, user): + def _revoke_all_policies_preserve_mfa(self, username, idp=None): """ If MFA is enabled for the user's idp, check if they have the /multifactor_auth resource and restore the mfa_policy after revoking all policies. """ - username = user.username - idp = user.identity_provider.name if user.identity_provider else None + user_data_from_arborist = None + try: + user_data_from_arborist = self.arborist_client.get_user(username) + except ArboristError: + # user doesn't exist in Arborist, nothing to revoke + return is_mfa_enabled = "multifactor_auth_claim_info" in config["OPENID_CONNECT"].get( idp, {} @@ -1886,7 +1895,7 @@ def _revoke_all_policies_preserve_mfa(self, user): policies = [] try: - policies = self.arborist_client.get_user()["policies"] + policies = user_data_from_arborist["policies"] except Exception as e: self.logger.error( f"Could not retrieve user's policies, revoking all policies anyway. {e}" @@ -1998,12 +2007,14 @@ def _update_authz_in_arborist( for username, user_project_info in user_projects.items(): self.logger.info("processing user `{}`".format(username)) user = query_for_user(session=session, username=username) + idp = None if user: username = user.username + idp = user.identity_provider.name if user.identity_provider else None self.arborist_client.create_user_if_not_exist(username) if not single_user_sync: - self._revoke_all_policies_preserve_mfa(user) + self._revoke_all_policies_preserve_mfa(username, idp) # as of 2/11/2022, for single_user_sync, as RAS visa parsing has # previously mapped each project to the same set of privileges diff --git a/fence/utils.py b/fence/utils.py index f0ae9a9d9..463fb6f75 100644 --- a/fence/utils.py +++ b/fence/utils.py @@ -18,7 +18,8 @@ from fence.models import Client, User, query_for_user from fence.errors import NotFound, UserError from fence.config import config - +from authlib.oauth2.rfc6749.util import scope_to_list +from authlib.oauth2.rfc6749.errors import InvalidScopeError rng = SystemRandom() alphanumeric = string.ascii_uppercase + string.ascii_lowercase + string.digits @@ -107,7 +108,7 @@ def create_client( client_secret=hashed_secret, user=user, redirect_uris=urls, - _allowed_scopes=" ".join(allowed_scopes), + allowed_scopes=" ".join(allowed_scopes), description=description, name=name, auto_approve=auto_approve, @@ -215,7 +216,7 @@ def clear_cookies(response): Set all cookies to empty and expired. """ for cookie_name in list(flask.request.cookies.keys()): - response.set_cookie(cookie_name, "", expires=0, httponly=True) + response.set_cookie(key=cookie_name, value="", expires=0, httponly=True) def get_error_params(error, description): @@ -433,3 +434,22 @@ def get_SQLAlchemyDriver(db_conn_url): "max_tries": config["DEFAULT_BACKOFF_SETTINGS_MAX_TRIES"], "giveup": exception_do_not_retry, } + + +def validate_scopes(request_scopes, client): + if not client: + raise Exception("Client object is None") + + if request_scopes: + scopes = scope_to_list(request_scopes) + # can we get some debug logs here that log the client, what scopes they have, and what scopes were requested + if not client.check_requested_scopes(set(scopes)): + logger.debug( + "Request Scope are " + + " ".join(scopes) + + " but client supported scopes are " + + client.scope + ) + raise InvalidScopeError("Failed to Authorize due to unsupported scope") + + return True diff --git a/migrations/models/migration_client.py b/migrations/models/migration_client.py new file mode 100644 index 000000000..bc818f09e --- /dev/null +++ b/migrations/models/migration_client.py @@ -0,0 +1,63 @@ +from authlib.integrations.sqla_oauth2 import OAuth2ClientMixin +from sqlalchemy import Boolean, Column, Integer, String, Text, func +from sqlalchemy.orm import Session, backref, relationship +from sqlalchemy.schema import ForeignKey +from userdatamodel import Base +from userdatamodel.models import User +import time + +# This needs to be in a different file +# Otherwise SqlAlchemy would import this multiple times and then complain about metadata conflict +class MigrationClient(Base): + + __tablename__ = "migration_client" + + client_id = Column(String(48), primary_key=True, index=True) + # this is hashed secret + client_secret = Column(String(120), unique=True, index=True, nullable=True) + + # human readable name + name = Column(String(40), nullable=False) + + # human readable description, not required + description = Column(String(400)) + + # required if you need to support client credential + user_id = Column(Integer) + + # this is for internal microservices to skip user grant + auto_approve = Column(Boolean, default=False) + + # public or confidential + is_confidential = Column(Boolean, default=True) + + issued_at = Column(Integer, nullable=False, default=lambda: int(time.time())) + + expires_at = Column(Integer, nullable=False, default=0) + + _redirect_uris = Column(Text) + + _allowed_scopes = Column(Text, nullable=False, default="") + + # Deprecated, keeping these around in case it is needed later + _default_scopes = Column(Text) + _scopes = ["compute", "storage", "user"] + + redirect_uri = Column(Text) + token_endpoint_auth_method = Column(String(48), default="client_secret_basic") + grant_type = Column(Text, nullable=False, default="") + response_type = Column(Text, nullable=False, default="") + scope = Column(Text, nullable=False, default="") + + client_name = Column(String(100)) + client_uri = Column(Text) + logo_uri = Column(Text) + contact = Column(Text) + tos_uri = Column(Text) + policy_uri = Column(Text) + jwks_uri = Column(Text) + jwks_text = Column(Text) + i18n_metadata = Column(Text) + + software_id = Column(String(36)) + software_version = Column(String(48)) diff --git a/migrations/versions/9b3a5a7145d7_authlib_update_1_2_1.py b/migrations/versions/9b3a5a7145d7_authlib_update_1_2_1.py new file mode 100644 index 000000000..43caadb28 --- /dev/null +++ b/migrations/versions/9b3a5a7145d7_authlib_update_1_2_1.py @@ -0,0 +1,296 @@ +"""authlib update 1.2.1 + +Revision ID: 9b3a5a7145d7 +Revises: a04a70296688 +Create Date: 2023-09-01 10:27:16.686456 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.orm import Session +from sqlalchemy.sql import text + +import json +from authlib.common.encoding import json_loads, json_dumps + +from migrations.models.migration_client import MigrationClient +from fence.models import Client + +# revision identifiers, used by Alembic. +revision = "9b3a5a7145d7" # pragma: allowlist secret +down_revision = "a04a70296688" # pragma: allowlist secret +branch_labels = None +depends_on = None + + +def upgrade(): + + temp_table_name = "migration_client" + # Make a copy of client table + copy_client_to_temp_table_and_clear_data(op, temp_table_name) + + # Add new columns for client table + op.add_column("client", sa.Column("client_metadata", sa.Text(), nullable=True)) + op.add_column( + "client", + sa.Column( + "client_secret_expires_at", sa.Integer(), nullable=False, server_default="0" + ), + ) + + # Modify columns for client table + op.alter_column("client", "issued_at", new_column_name="client_id_issued_at") + op.alter_column("client", "client_id", nullable=False, type_=sa.String(48)) + op.alter_column("client", "client_secret", nullable=True, type_=sa.String(120)) + + # Delete old columns for client table + op.drop_column("client", "redirect_uri") + op.drop_column("client", "token_endpoint_auth_method") + op.drop_column("client", "grant_type") + op.drop_column("client", "response_type") + op.drop_column("client", "scope") + op.drop_column("client", "client_name") + op.drop_column("client", "client_uri") + op.drop_column("client", "logo_uri") + op.drop_column("client", "contact") + op.drop_column("client", "tos_uri") + op.drop_column("client", "policy_uri") + op.drop_column("client", "jwks_uri") + op.drop_column("client", "jwks_text") + op.drop_column("client", "i18n_metadata") + op.drop_column("client", "software_id") + op.drop_column("client", "software_version") + op.drop_column("client", "_allowed_scopes") + op.drop_column("client", "_redirect_uris") + + transform_client_data(op) + + # Drop temp table + op.drop_table(temp_table_name) + + # Add New Columns for authorization_code Table + op.add_column( + "authorization_code", sa.Column("code_challenge", sa.Text(), nullable=True) + ) + op.add_column( + "authorization_code", + sa.Column("code_challenge_method", sa.String(length=48), nullable=True), + ) + + +def downgrade(): + + temp_table_name = "migration_client" + # Make a copy of client table + copy_client_to_temp_table_and_clear_data(op, temp_table_name) + + # Add Old Columns Back + op.add_column("client", sa.Column("redirect_uri", sa.Text(), nullable=True)) + op.add_column( + "client", + sa.Column("token_endpoint_auth_method", sa.String(length=48), nullable=True), + ) + op.add_column( + "client", sa.Column("grant_type", sa.Text(), nullable=False, server_default="") + ) + op.add_column( + "client", + sa.Column("response_type", sa.Text(), nullable=False, server_default=""), + ) + op.add_column( + "client", sa.Column("scope", sa.Text(), nullable=False, server_default="") + ) + op.add_column( + "client", sa.Column("client_name", sa.String(length=100), nullable=True) + ) + op.add_column("client", sa.Column("client_uri", sa.Text(), nullable=True)) + op.add_column("client", sa.Column("logo_uri", sa.Text(), nullable=True)) + op.add_column("client", sa.Column("contact", sa.Text(), nullable=True)) + op.add_column("client", sa.Column("tos_uri", sa.Text(), nullable=True)) + op.add_column("client", sa.Column("policy_uri", sa.Text(), nullable=True)) + op.add_column("client", sa.Column("jwks_uri", sa.Text(), nullable=True)) + op.add_column("client", sa.Column("jwks_text", sa.Text(), nullable=True)) + op.add_column("client", sa.Column("i18n_metadata", sa.Text(), nullable=True)) + op.add_column( + "client", sa.Column("software_id", sa.String(length=36), nullable=True) + ) + op.add_column( + "client", sa.Column("software_version", sa.String(length=48), nullable=True) + ) + op.add_column( + "client", + sa.Column("_allowed_scopes", sa.Text(), nullable=False, server_default=""), + ) + op.add_column("client", sa.Column("_redirect_uris", sa.Text(), nullable=True)) + + # Modify Columns for client Table + op.alter_column("client", "client_id_issued_at", new_column_name="issued_at") + op.alter_column("client", "client_id", nullable=False, type_=sa.String(40)) + op.alter_column("client", "client_secret", nullable=True, type_=sa.String(60)) + + # Drop New Columns for client Table + op.drop_column("client", "client_metadata") + op.drop_column("client", "client_secret_expires_at") + + # Set value of old columns + set_old_column_values() + op.drop_table(temp_table_name) + + # Remove New Columns for authorization_code Table + op.drop_column("authorization_code", "code_challenge") + op.drop_column("authorization_code", "code_challenge_method") + + +def copy_client_to_temp_table_and_clear_data(op, temp_table_name: str): + """Copy client table schema and data into temp table""" + conn = op.get_bind() + session = Session(bind=conn) + # Drop temp table if it already exists + # copy client table with all table metadata then copy all row data + session.execute("DROP TABLE IF EXISTS " + temp_table_name + ";") + session.execute("CREATE TABLE " + temp_table_name + " (LIKE client INCLUDING ALL);") + session.execute("INSERT INTO " + temp_table_name + " SELECT * FROM client;") + session.execute("Truncate client;") + session.commit() + + +def transform_client_data(op): + conn = op.get_bind() + session = Session(bind=conn) + + for client in session.query(MigrationClient).all(): + if client.i18n_metadata: + metadata = json.loads(client.i18n_metadata) + else: + metadata = {} + + if client.redirect_uri: + metadata["redirect_uris"] = client.redirect_uri.splitlines() + if client.token_endpoint_auth_method: + metadata["token_endpoint_auth_method"] = client.token_endpoint_auth_method + if client.grant_type: + metadata["grant_types"] = client.grant_type.splitlines() + if client.response_type: + metadata["response_types"] = client.response_type.splitlines() + if client.client_uri: + metadata["client_uri"] = client.client_uri + if client.logo_uri: + metadata["logo_uri"] = client.logo_uri + if client.contact: + metadata["contact"] = client.contact + if client.tos_uri: + metadata["tos_uri"] = client.tos_uri + if client.policy_uri: + metadata["policy_uri"] = client.policy_uri + if client.jwks_uri: + metadata["jwks_uri"] = client.jwks_uri + if client.jwks_text: + metadata["jwks_text"] = client.jwks_text + if client.software_id: + metadata["software_id"] = client.software_id + if client.software_version: + metadata["software_version"] = client.software_version + + new_client = Client( + client_id=client.client_id, + client_secret=client.client_secret, + name=client.name, + description=client.description, + allowed_scopes=client._allowed_scopes.split(" "), + user_id=client.user_id, + auto_approve=client.auto_approve, + is_confidential=client.is_confidential, + expires_at=client.expires_at, + _default_scopes=client._default_scopes, + grant_types=client.grant_type.splitlines(), + response_types=client.response_type.splitlines(), + client_id_issued_at=client.issued_at, + _client_metadata=json_dumps(metadata), + ) + + session.add(new_client) + + session.commit() + + +def set_old_column_values(): + conn = op.get_bind() + session = Session(bind=conn) + clientDatas = [] + + rs = session.execute("SELECT * FROM migration_client") + for client in rs: + data = {} + data["client_id"] = client.client_id + data["client_secret"] = client.client_secret + data["name"] = client.name + data["description"] = client.description + data["user_id"] = client.user_id + data["auto_approve"] = client.auto_approve + data["is_confidential"] = client.is_confidential + data["expires_at"] = client.expires_at + data["issued_at"] = client.client_id_issued_at + data["_default_scopes"] = client._default_scopes + data["_redirect_uris"] = None # Deprecated + data["scope"] = "" # Deprecated + data["client_name"] = None + + if client.client_metadata: + metadata = json_loads(client.client_metadata) + data["i18n_metadata"] = client.client_metadata + else: + metadata = {} + data["i18n_metadata"] = None + + if metadata.get("redirect_uris"): + data["redirect_uri"] = "\n".join( + [item for item in metadata.get("redirect_uris") if item] + ) + else: + data["redirect_uri"] = "" + + data["token_endpoint_auth_method"] = metadata.get("token_endpoint_auth_method") + data["_allowed_scopes"] = metadata.get("scope") + + if metadata.get("grant_types"): + data["grant_type"] = "\n".join( + [item for item in metadata.get("grant_types") if item] + ) + else: + data["grant_type"] = "" + + if metadata.get("response_types"): + data["response_type"] = "\n".join( + [item for item in metadata.get("response_types") if item] + ) + else: + data["response_type"] = "" + + data["client_uri"] = metadata.get("client_uri") + data["logo_uri"] = metadata.get("logo_uri") + data["contact"] = metadata.get("contact") + data["tos_uri"] = metadata.get("tos_uri") + data["policy_uri"] = metadata.get("policy_uri") + data["jwks_uri"] = metadata.get("jwks_uri") + data["jwks_text"] = metadata.get("jwks_text") + data["software_id"] = metadata.get("software_id") + data["software_version"] = metadata.get("software_version") + + clientDatas.append(data) + + statement = text( + """INSERT INTO client(client_id, client_secret, name, description, + user_id, auto_approve, is_confidential, issued_at, expires_at, _redirect_uris, _allowed_scopes, + _default_scopes, redirect_uri, token_endpoint_auth_method, grant_type, response_type, scope, + client_name,client_uri,logo_uri,contact,tos_uri,policy_uri,jwks_uri,jwks_text,i18n_metadata, + software_id,software_version) + VALUES( :client_id, :client_secret, :name, :description, :user_id, :auto_approve, :is_confidential, :issued_at, + :expires_at, :_redirect_uris, :_allowed_scopes, :_default_scopes, :redirect_uri, :token_endpoint_auth_method, + :grant_type, :response_type, :scope, :client_name, :client_uri, :logo_uri, :contact, :tos_uri, :policy_uri, + :jwks_uri, :jwks_text, :i18n_metadata, :software_id, :software_version)""" + ) + + for data in clientDatas: + conn.execute(statement, **data) + + session.commit() diff --git a/poetry.lock b/poetry.lock index e4bf0fe1b..4f2a43732 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "addict" version = "2.4.0" description = "Addict is a dictionary whose items can be set using both attribute and item syntax." -category = "dev" optional = false python-versions = "*" files = [ @@ -14,14 +13,13 @@ files = [ [[package]] name = "alembic" -version = "1.11.2" +version = "1.13.1" description = "A database migration tool for SQLAlchemy." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "alembic-1.11.2-py3-none-any.whl", hash = "sha256:7981ab0c4fad4fe1be0cf183aae17689fe394ff874fd2464adb774396faf0796"}, - {file = "alembic-1.11.2.tar.gz", hash = "sha256:678f662130dc540dac12de0ea73de9f89caea9dbea138f60ef6263149bf84657"}, + {file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"}, + {file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"}, ] [package.dependencies] @@ -30,13 +28,12 @@ SQLAlchemy = ">=1.3.0" typing-extensions = ">=4" [package.extras] -tz = ["python-dateutil"] +tz = ["backports.zoneinfo"] [[package]] name = "aniso8601" version = "9.0.1" description = "A library for parsing ISO 8601 strings." -category = "main" optional = false python-versions = "*" files = [ @@ -49,31 +46,30 @@ dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"] [[package]] name = "anyio" -version = "3.7.1" +version = "4.4.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, - {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, ] [package.dependencies] -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] -doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] -test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (<0.22)"] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.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.*" files = [ @@ -82,72 +78,65 @@ files = [ [[package]] name = "attrs" -version = "23.1.0" +version = "23.2.0" description = "Classes Without Boilerplate" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]", "pre-commit"] +dev = ["attrs[tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] -name = "Authlib" -version = "0.11" -description = "The ultimate Python library in building OAuth and OpenID Connect servers." -category = "main" +name = "authlib" +version = "1.3.1" +description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." optional = false -python-versions = "*" -files = [] -develop = false +python-versions = ">=3.8" +files = [ + {file = "Authlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:d35800b973099bbadc49b42b256ecb80041ad56b7fe1216a362c7943c088f377"}, + {file = "authlib-1.3.1.tar.gz", hash = "sha256:7ae843f03c06c5c0debd63c9db91f9fda64fa62a42a77419fa15fbb7e7a58917"}, +] [package.dependencies] cryptography = "*" -requests = "*" - -[package.source] -type = "git" -url = "https://github.com/uc-cdis/authlib" -reference = "v0.11_CVE_patch_v1" -resolved_reference = "80345f2877ec2a1a29468aa465c07623347c3ef6" [[package]] name = "authutils" -version = "6.2.2" +version = "6.2.5" description = "Gen3 auth utility functions" -category = "main" optional = false -python-versions = ">=3.9,<4.0" +python-versions = "<4.0,>=3.9" files = [ - {file = "authutils-6.2.2-py3-none-any.whl", hash = "sha256:df9b551b4ab561452f0f4b50edaddccc443905b4d77ee69ea7eea78938e7caed"}, - {file = "authutils-6.2.2.tar.gz", hash = "sha256:ded3e5c0e35160eab83bfb217976920396441e19ed977acacbb769e988323850"}, + {file = "authutils-6.2.5-py3-none-any.whl", hash = "sha256:ef91c9c7c750123c28b7376be9ca00b4e89b2d52fa183dec9bfe681d8eac6227"}, + {file = "authutils-6.2.5.tar.gz", hash = "sha256:0d496721e9f0d8c69b34aff8f6fccdc7768ca4f104504d68e70fd647d4c23b19"}, ] [package.dependencies] -authlib = "0.11.0" +authlib = ">=1.1.0" cached-property = ">=1.4,<2.0" cdiserrors = "<2.0.0" +cryptography = ">=41.0.6" httpx = ">=0.23.0,<1.0.0" pyjwt = {version = ">=2.4.0,<3.0", extras = ["crypto"]} xmltodict = ">=0.9,<1.0" [package.extras] fastapi = ["fastapi (>=0.65.2,<0.66.0)"] -flask = ["Flask (>=0.10.1)"] +flask = ["Flask (<=2.3.3)"] [[package]] name = "aws-xray-sdk" version = "0.95" description = "The AWS X-Ray SDK for Python (the SDK) enables Python developers to record and emit information from within their applications to the AWS X-Ray service." -category = "dev" optional = false python-versions = "*" files = [ @@ -162,50 +151,47 @@ wrapt = "*" [[package]] name = "azure-core" -version = "1.28.0" +version = "1.30.2" description = "Microsoft Azure Core Library for Python" -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "azure-core-1.28.0.zip", hash = "sha256:e9eefc66fc1fde56dab6f04d4e5d12c60754d5a9fa49bdcfd8534fc96ed936bd"}, - {file = "azure_core-1.28.0-py3-none-any.whl", hash = "sha256:dec36dfc8eb0b052a853f30c07437effec2f9e3e1fc8f703d9bdaa5cfc0043d9"}, + {file = "azure-core-1.30.2.tar.gz", hash = "sha256:a14dc210efcd608821aa472d9fb8e8d035d29b68993819147bc290a8ac224472"}, + {file = "azure_core-1.30.2-py3-none-any.whl", hash = "sha256:cf019c1ca832e96274ae85abd3d9f752397194d9fea3b41487290562ac8abe4a"}, ] [package.dependencies] -requests = ">=2.18.4" +requests = ">=2.21.0" six = ">=1.11.0" -typing-extensions = ">=4.3.0" +typing-extensions = ">=4.6.0" [package.extras] aio = ["aiohttp (>=3.0)"] [[package]] name = "azure-storage-blob" -version = "12.17.0" +version = "12.20.0" description = "Microsoft Azure Blob Storage Client Library for Python" -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "azure-storage-blob-12.17.0.zip", hash = "sha256:c14b785a17050b30fc326a315bdae6bc4a078855f4f94a4c303ad74a48dc8c63"}, - {file = "azure_storage_blob-12.17.0-py3-none-any.whl", hash = "sha256:0016e0c549a80282d7b4920c03f2f4ba35c53e6e3c7dbcd2a4a8c8eb3882c1e7"}, + {file = "azure-storage-blob-12.20.0.tar.gz", hash = "sha256:eeb91256e41d4b5b9bad6a87fd0a8ade07dd58aa52344e2c8d2746e27a017d3b"}, + {file = "azure_storage_blob-12.20.0-py3-none-any.whl", hash = "sha256:de6b3bf3a90e9341a6bcb96a2ebe981dffff993e9045818f6549afea827a52a9"}, ] [package.dependencies] -azure-core = ">=1.28.0,<2.0.0" +azure-core = ">=1.28.0" cryptography = ">=2.1.4" isodate = ">=0.6.1" -typing-extensions = ">=4.3.0" +typing-extensions = ">=4.6.0" [package.extras] -aio = ["azure-core[aio] (>=1.28.0,<2.0.0)"] +aio = ["azure-core[aio] (>=1.28.0)"] [[package]] name = "backoff" version = "1.11.1" description = "Function decoration for backoff and retry" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -217,7 +203,6 @@ files = [ name = "bcrypt" version = "3.2.2" description = "Modern password hashing for your software and your servers" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -243,21 +228,19 @@ typecheck = ["mypy"] [[package]] name = "blinker" -version = "1.6.2" +version = "1.8.2" description = "Fast, simple object-to-object and broadcast signaling" -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "blinker-1.6.2-py3-none-any.whl", hash = "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0"}, - {file = "blinker-1.6.2.tar.gz", hash = "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213"}, + {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, + {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, ] [[package]] name = "boto" version = "2.49.0" description = "Amazon Web Services Library" -category = "main" optional = false python-versions = "*" files = [ @@ -267,44 +250,49 @@ files = [ [[package]] name = "boto3" -version = "1.9.253" +version = "1.34.128" description = "The AWS SDK for Python" -category = "main" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "boto3-1.9.253-py2.py3-none-any.whl", hash = "sha256:839285fbd6f3ab16170af449ae9e33d0eccf97ca22de17d9ff68b8da2310ea06"}, - {file = "boto3-1.9.253.tar.gz", hash = "sha256:d93f1774c4bc66e02acdda2067291acb9e228a035435753cb75f83ad2904cbe3"}, + {file = "boto3-1.34.128-py3-none-any.whl", hash = "sha256:a048ff980a81cd652724a73bc496c519b336fabe19cc8bfc6c53b2ff6eb22c7b"}, + {file = "boto3-1.34.128.tar.gz", hash = "sha256:43a6e99f53a8d34b3b4dbe424dbcc6b894350dc41a85b0af7c7bc24a7ec2cead"}, ] [package.dependencies] -botocore = ">=1.12.253,<1.13.0" -jmespath = ">=0.7.1,<1.0.0" -s3transfer = ">=0.2.0,<0.3.0" +botocore = ">=1.34.128,<1.35.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.10.0,<0.11.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.12.253" +version = "1.34.128" description = "Low-level, data-driven core of boto 3." -category = "main" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "botocore-1.12.253-py2.py3-none-any.whl", hash = "sha256:dc080aed4f9b220a9e916ca29ca97a9d37e8e1d296fe89cbaeef929bf0c8066b"}, - {file = "botocore-1.12.253.tar.gz", hash = "sha256:3baf129118575602ada9926f5166d82d02273c250d0feb313fc270944b27c48b"}, + {file = "botocore-1.34.128-py3-none-any.whl", hash = "sha256:db67fda136c372ab3fa432580c819c89ba18d28a6152a4d2a7ea40d44082892e"}, + {file = "botocore-1.34.128.tar.gz", hash = "sha256:8d8e03f7c8c080ecafda72036eb3b482d649f8417c90b5dca33b7c2c47adb0c9"}, ] [package.dependencies] -docutils = ">=0.10,<0.16" -jmespath = ">=0.7.1,<1.0.0" -python-dateutil = {version = ">=2.1,<3.0.0", markers = "python_version >= \"2.7\""} -urllib3 = {version = ">=1.20,<1.26", markers = "python_version >= \"3.4\""} +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = [ + {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, + {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, +] + +[package.extras] +crt = ["awscrt (==0.20.11)"] [[package]] name = "cached-property" version = "1.5.2" description = "A decorator for caching properties in classes." -category = "main" optional = false python-versions = "*" files = [ @@ -316,7 +304,6 @@ files = [ name = "cachelib" version = "0.2.0" description = "A collection of cache libraries in the same API interface." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -326,21 +313,19 @@ files = [ [[package]] name = "cachetools" -version = "4.2.4" +version = "5.3.3" description = "Extensible memoizing collections and decorators" -category = "main" optional = false -python-versions = "~=3.5" +python-versions = ">=3.7" files = [ - {file = "cachetools-4.2.4-py3-none-any.whl", hash = "sha256:92971d3cb7d2a97efff7c7bb1657f21a8f5fb309a37530537c71b1774189f2d1"}, - {file = "cachetools-4.2.4.tar.gz", hash = "sha256:89ea6f1b638d5a73a4f9226be57ac5e4f399d22770b92355f92dcb0f7f001693"}, + {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, + {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, ] [[package]] name = "cdiserrors" version = "1.0.0" description = "Gen3 shared exceptions and utilities." -category = "main" optional = false python-versions = ">=3.6,<4.0" files = [ @@ -356,20 +341,18 @@ flask = ["Flask (>=1.1.2,<2.0.0)"] [[package]] name = "cdislogging" -version = "1.0.0" +version = "1.1.1" description = "Standardized logging tool and format for cdis applications" -category = "main" optional = false python-versions = "*" files = [ - {file = "cdislogging-1.0.0.tar.gz", hash = "sha256:a1cc2e48d5fc26d4b354b80c6497f1f1136f3e3e4f1d1855de8980ccf497fa0a"}, + {file = "cdislogging-1.1.1.tar.gz", hash = "sha256:77e11648244cda3a8094b8ae6081435a2303f259612846c49ef8825c7be141e3"}, ] [[package]] name = "cdispyutils" version = "2.0.1" description = "This package includes several utility Python tools for the Gen3 stack." -category = "main" optional = false python-versions = ">=3.6,<4.0" files = [ @@ -386,9 +369,8 @@ requests = "*" [[package]] name = "cdisutilstest" -version = "0.2.4" +version = "2.0.0" description = "Collection of test data and tools" -category = "dev" optional = false python-versions = "*" files = [] @@ -397,93 +379,79 @@ develop = false [package.source] type = "git" url = "https://github.com/uc-cdis/cdisutils-test" -reference = "1.0.0" -resolved_reference = "bdfdeb05e45407e839fd954ce6d195d847cd8024" +reference = "2.0.0" +resolved_reference = "74a607736ca4af5ec35f17830ab9b78b5db15837" [[package]] name = "certifi" -version = "2023.7.22" +version = "2024.6.2" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, + {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, ] [[package]] name = "cffi" -version = "1.15.1" +version = "1.16.0" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, ] [package.dependencies] @@ -491,99 +459,112 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.2.0" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, - {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] name = "click" -version = "8.1.6" +version = "8.1.7" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, - {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -593,7 +574,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "codacy-coverage" version = "1.3.11" description = "Codacy coverage reporter for Python" -category = "dev" optional = false python-versions = "*" files = [ @@ -612,7 +592,6 @@ test = ["coverage", "nosetests"] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -624,7 +603,6 @@ files = [ name = "coverage" version = "5.5" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" files = [ @@ -689,7 +667,6 @@ toml = ["toml"] name = "coveralls" version = "2.2.0" description = "Show coverage stats online via coveralls.io" -category = "dev" optional = false python-versions = ">= 3.5" files = [ @@ -707,55 +684,62 @@ yaml = ["PyYAML (>=3.10)"] [[package]] name = "cryptography" -version = "41.0.3" +version = "42.0.8" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, - {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, - {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, - {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, -] - -[package.dependencies] -cffi = ">=1.12" + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, + {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, + {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, + {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, + {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, + {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, + {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] -pep8test = ["black", "check-sdist", "mypy", "ruff"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "decorator" version = "5.1.1" description = "Decorators for Humans" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -765,79 +749,65 @@ files = [ [[package]] name = "dnspython" -version = "2.4.1" +version = "2.6.1" description = "DNS toolkit" -category = "main" optional = false -python-versions = ">=3.8,<4.0" +python-versions = ">=3.8" files = [ - {file = "dnspython-2.4.1-py3-none-any.whl", hash = "sha256:5b7488477388b8c0b70a8ce93b227c5603bc7b77f1565afe8e729c36c51447d7"}, - {file = "dnspython-2.4.1.tar.gz", hash = "sha256:c33971c79af5be968bb897e95c2448e11a645ee84d93b265ce0b7aabe5dfdca8"}, + {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, + {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, ] [package.extras] -dnssec = ["cryptography (>=2.6,<42.0)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"] -doq = ["aioquic (>=0.9.20)"] -idna = ["idna (>=2.1,<4.0)"] -trio = ["trio (>=0.14,<0.23)"] -wmi = ["wmi (>=1.5.1,<2.0.0)"] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=0.9.25)"] +idna = ["idna (>=3.6)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] [[package]] name = "docker" -version = "5.0.3" +version = "7.1.0" description = "A Python library for the Docker Engine API." -category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "docker-5.0.3-py2.py3-none-any.whl", hash = "sha256:7a79bb439e3df59d0a72621775d600bc8bc8b422d285824cb37103eab91d1ce0"}, - {file = "docker-5.0.3.tar.gz", hash = "sha256:d916a26b62970e7c2f554110ed6af04c7ccff8e9f81ad17d0d40c75637e227fb"}, + {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"}, + {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"}, ] [package.dependencies] -pywin32 = {version = "227", markers = "sys_platform == \"win32\""} -requests = ">=2.14.2,<2.18.0 || >2.18.0" -websocket-client = ">=0.32.0" +pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} +requests = ">=2.26.0" +urllib3 = ">=1.26.0" [package.extras] -ssh = ["paramiko (>=2.4.2)"] -tls = ["cryptography (>=3.4.7)", "idna (>=2.0.0)", "pyOpenSSL (>=17.5.0)"] +dev = ["coverage (==7.2.7)", "pytest (==7.4.2)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.1.0)", "ruff (==0.1.8)"] +docs = ["myst-parser (==0.18.0)", "sphinx (==5.1.1)"] +ssh = ["paramiko (>=2.4.3)"] +websockets = ["websocket-client (>=1.3.0)"] [[package]] name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" -category = "dev" optional = false python-versions = "*" files = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] -[[package]] -name = "docutils" -version = "0.15.2" -description = "Docutils -- Python Documentation Utilities" -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "docutils-0.15.2-py2-none-any.whl", hash = "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827"}, - {file = "docutils-0.15.2-py3-none-any.whl", hash = "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0"}, - {file = "docutils-0.15.2.tar.gz", hash = "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"}, -] - [[package]] name = "ecdsa" -version = "0.18.0" +version = "0.19.0" description = "ECDSA cryptographic signature library (pure python)" -category = "main" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.6" files = [ - {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, - {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, + {file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"}, + {file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"}, ] [package.dependencies] @@ -851,7 +821,6 @@ gmpy2 = ["gmpy2"] name = "email-validator" version = "1.3.1" description = "A robust email address syntax and deliverability validation library." -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -865,14 +834,13 @@ idna = ">=2.0.0" [[package]] name = "exceptiongroup" -version = "1.1.2" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, - {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -880,14 +848,13 @@ test = ["pytest (>=6)"] [[package]] name = "flask" -version = "2.3.2" +version = "3.0.3" description = "A simple framework for building complex web applications." -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "Flask-2.3.2-py3-none-any.whl", hash = "sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0"}, - {file = "Flask-2.3.2.tar.gz", hash = "sha256:8c2f9abd47a9e8df7f0c3f091ce9497d011dc3b31effcf4c85a6e2b50f4114ef"}, + {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, + {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, ] [package.dependencies] @@ -896,7 +863,7 @@ click = ">=8.1.3" importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} itsdangerous = ">=2.1.2" Jinja2 = ">=3.1.2" -Werkzeug = ">=2.3.3" +Werkzeug = ">=3.0.0" [package.extras] async = ["asgiref (>=3.2)"] @@ -904,14 +871,13 @@ dotenv = ["python-dotenv"] [[package]] name = "flask-cors" -version = "4.0.0" +version = "4.0.1" description = "A Flask extension adding a decorator for CORS support" -category = "main" optional = false python-versions = "*" files = [ - {file = "Flask-Cors-4.0.0.tar.gz", hash = "sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0"}, - {file = "Flask_Cors-4.0.0-py2.py3-none-any.whl", hash = "sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783"}, + {file = "Flask_Cors-4.0.1-py2.py3-none-any.whl", hash = "sha256:f2a704e4458665580c074b714c4627dd5a306b333deb9074d0b1794dfa2fb677"}, + {file = "flask_cors-4.0.1.tar.gz", hash = "sha256:eeb69b342142fdbf4766ad99357a7f3876a2ceb77689dc10ff912aac06c389e4"}, ] [package.dependencies] @@ -921,7 +887,6 @@ Flask = ">=0.9" name = "flask-restful" version = "0.3.10" description = "Simple framework for creating REST APIs" -category = "main" optional = false python-versions = "*" files = [ @@ -940,20 +905,19 @@ docs = ["sphinx"] [[package]] name = "flask-wtf" -version = "1.1.1" +version = "1.2.1" description = "Form rendering, validation, and CSRF protection for Flask with WTForms." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Flask-WTF-1.1.1.tar.gz", hash = "sha256:41c4244e9ae626d63bed42ae4785b90667b885b1535d5a4095e1f63060d12aa9"}, - {file = "Flask_WTF-1.1.1-py3-none-any.whl", hash = "sha256:7887d6f1ebb3e17bf648647422f0944c9a469d0fcf63e3b66fb9a83037e38b2c"}, + {file = "flask_wtf-1.2.1-py3-none-any.whl", hash = "sha256:fa6793f2fb7e812e0fe9743b282118e581fb1b6c45d414b8af05e659bd653287"}, + {file = "flask_wtf-1.2.1.tar.gz", hash = "sha256:8bb269eb9bb46b87e7c8233d7e7debdf1f8b74bf90cc1789988c29b37a97b695"}, ] [package.dependencies] -Flask = "*" +flask = "*" itsdangerous = "*" -WTForms = "*" +wtforms = "*" [package.extras] email = ["email-validator"] @@ -962,7 +926,6 @@ email = ["email-validator"] name = "future" version = "0.18.3" description = "Clean single-source support for Python 3 and 2" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -973,7 +936,6 @@ files = [ name = "gen3authz" version = "1.5.1" description = "Gen3 authz client" -category = "main" optional = false python-versions = ">=3.6,<4.0" files = [ @@ -989,29 +951,28 @@ six = ">=1.16.0,<2.0.0" [[package]] name = "gen3cirrus" -version = "2.0.0" +version = "3.0.1" description = "" -category = "main" optional = false -python-versions = "*" +python-versions = ">=3.9,<4.0" files = [ - {file = "gen3cirrus-2.0.0.tar.gz", hash = "sha256:0bd590c407c42dad5f0b896da0fa30bd01ea6bef5ff7dd11324ec59f14a71793"}, + {file = "gen3cirrus-3.0.1-py3-none-any.whl", hash = "sha256:74628faca3b1cbe65c78e08eb567e1ac0cb8ae52e1bfc603f904af0277e3cb52"}, + {file = "gen3cirrus-3.0.1.tar.gz", hash = "sha256:0ae0ddc0ee7df870603457fe186245f3c8124d989254276e5011a23e1139a6c8"}, ] [package.dependencies] -backoff = ">=1.6,<2.0" +backoff = "*" cdislogging = "*" -google-api-python-client = "1.11.0" -google-auth = ">=1.4,<2.0" -google-auth-httplib2 = ">=0.0.3" -google-cloud-storage = ">=1.10,<2.0" -oauth2client = ">=2.0.0,<4.0dev" +google-api-python-client = "*" +google-auth = "*" +google-auth-httplib2 = "*" +google-cloud-storage = "*" +oauth2client = "*" [[package]] name = "gen3config" version = "1.1.0" description = "Gen3 Configuration Library" -category = "main" optional = false python-versions = "^3.9" files = [ @@ -1027,149 +988,143 @@ six = "*" [[package]] name = "gen3users" -version = "0.6.0" -description = "Utils for Gen3 commons user management" -category = "main" +version = "1.0.3" +description = "Utils for Gen3 Commons user management" optional = false -python-versions = "*" +python-versions = ">=3.9,<4.0" files = [ - {file = "gen3users-0.6.0.tar.gz", hash = "sha256:3b9b56798a7d8b34712389dbbab93c00b0f92524f890513f899c31630ea986da"}, + {file = "gen3users-1.0.3-py3-none-any.whl", hash = "sha256:faf07717b7df28ea2c25a308e49c65d8ed69e14945c6f36e99deb697240bb8bb"}, + {file = "gen3users-1.0.3.tar.gz", hash = "sha256:a2269433ab886c23db37050144821405c7d5dfcbbadccc43302611aad9e34525"}, ] [package.dependencies] -cdislogging = ">=1.0.0,<1.1.0" +cdislogging = ">=1,<2" click = "*" -PyYAML = ">=5.1,<6.0" +pyyaml = ">=6,<7" [[package]] name = "google-api-core" -version = "1.34.0" +version = "2.19.0" description = "Google API client core library" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "google-api-core-1.34.0.tar.gz", hash = "sha256:6fb380f49d19ee1d09a9722d0379042b7edb06c0112e4796c7a395078a043e71"}, - {file = "google_api_core-1.34.0-py3-none-any.whl", hash = "sha256:7421474c39d396a74dfa317dddbc69188f2336835f526087c7648f91105e32ff"}, + {file = "google-api-core-2.19.0.tar.gz", hash = "sha256:cf1b7c2694047886d2af1128a03ae99e391108a08804f87cfd35970e49c9cd10"}, + {file = "google_api_core-2.19.0-py3-none-any.whl", hash = "sha256:8661eec4078c35428fd3f69a2c7ee29e342896b70f01d1a1cbcb334372dd6251"}, ] [package.dependencies] -google-auth = ">=1.25.0,<3.0dev" -googleapis-common-protos = ">=1.56.2,<2.0dev" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.0.0dev" -requests = ">=2.18.0,<3.0.0dev" +google-auth = ">=2.14.1,<3.0.dev0" +googleapis-common-protos = ">=1.56.2,<2.0.dev0" +proto-plus = ">=1.22.3,<2.0.0dev" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +requests = ">=2.18.0,<3.0.0.dev0" [package.extras] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio-status (>=1.33.2,<2.0dev)"] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "1.11.0" +version = "2.133.0" description = "Google API Client Library for Python" -category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +python-versions = ">=3.7" files = [ - {file = "google-api-python-client-1.11.0.tar.gz", hash = "sha256:caf4015800ef1a18d06d117f47f0219c0c0641f21978f6b1bb5ede7912fab97b"}, - {file = "google_api_python_client-1.11.0-py2.py3-none-any.whl", hash = "sha256:4f596894f702736da84cf89490a810b55ca02a81f0cddeacb3022e2900b11ec6"}, + {file = "google-api-python-client-2.133.0.tar.gz", hash = "sha256:293092905b66a046d3187a99ac454e12b00cc2c70444f26eb2f1f9c1a82720b4"}, + {file = "google_api_python_client-2.133.0-py2.py3-none-any.whl", hash = "sha256:396fe676ea0dfed066654dcf9f8dea77a1342f9d9bb23bb88e45b7b81e773926"}, ] [package.dependencies] -google-api-core = ">=1.18.0,<2dev" -google-auth = ">=1.16.0" -google-auth-httplib2 = ">=0.0.3" -httplib2 = ">=0.9.2,<1dev" -six = ">=1.6.1,<2dev" -uritemplate = ">=3.0.0,<4dev" +google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0.dev0" +google-auth = ">=1.32.0,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0.dev0" +google-auth-httplib2 = ">=0.2.0,<1.0.0" +httplib2 = ">=0.19.0,<1.dev0" +uritemplate = ">=3.0.1,<5" [[package]] name = "google-auth" -version = "1.35.0" +version = "2.30.0" description = "Google Authentication Library" -category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" +python-versions = ">=3.7" files = [ - {file = "google-auth-1.35.0.tar.gz", hash = "sha256:b7033be9028c188ee30200b204ea00ed82ea1162e8ac1df4aa6ded19a191d88e"}, - {file = "google_auth-1.35.0-py2.py3-none-any.whl", hash = "sha256:997516b42ecb5b63e8d80f5632c1a61dddf41d2a4c2748057837e06e00014258"}, + {file = "google-auth-2.30.0.tar.gz", hash = "sha256:ab630a1320f6720909ad76a7dbdb6841cdf5c66b328d690027e4867bdfb16688"}, + {file = "google_auth-2.30.0-py2.py3-none-any.whl", hash = "sha256:8df7da660f62757388b8a7f249df13549b3373f24388cb5d2f1dd91cc18180b5"}, ] [package.dependencies] -cachetools = ">=2.0.0,<5.0" +cachetools = ">=2.0.0,<6.0" pyasn1-modules = ">=0.2.1" -rsa = {version = ">=3.1.4,<5", markers = "python_version >= \"3.6\""} -setuptools = ">=40.3.0" -six = ">=1.9.0" +rsa = ">=3.1.4,<5" [package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "requests (>=2.20.0,<3.0.0dev)"] -pyopenssl = ["pyopenssl (>=20.0.0)"] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.20.0,<3.0.0.dev0)"] [[package]] name = "google-auth-httplib2" -version = "0.1.0" +version = "0.2.0" description = "Google Authentication Library: httplib2 transport" -category = "main" optional = false python-versions = "*" files = [ - {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, - {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, + {file = "google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05"}, + {file = "google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d"}, ] [package.dependencies] google-auth = "*" -httplib2 = ">=0.15.0" -six = "*" +httplib2 = ">=0.19.0" [[package]] name = "google-cloud-core" -version = "2.3.3" +version = "2.4.1" description = "Google Cloud API client core library" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "google-cloud-core-2.3.3.tar.gz", hash = "sha256:37b80273c8d7eee1ae816b3a20ae43585ea50506cb0e60f3cf5be5f87f1373cb"}, - {file = "google_cloud_core-2.3.3-py2.py3-none-any.whl", hash = "sha256:fbd11cad3e98a7e5b0343dc07cb1039a5ffd7a5bb96e1f1e27cee4bda4a90863"}, + {file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"}, + {file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"}, ] [package.dependencies] -google-api-core = ">=1.31.6,<2.0.0 || >2.3.0,<3.0.0dev" +google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev" google-auth = ">=1.25.0,<3.0dev" [package.extras] -grpc = ["grpcio (>=1.38.0,<2.0dev)"] +grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] [[package]] name = "google-cloud-storage" -version = "1.44.0" +version = "2.17.0" description = "Google Cloud Storage API client library" -category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" +python-versions = ">=3.7" files = [ - {file = "google-cloud-storage-1.44.0.tar.gz", hash = "sha256:29edbfeedd157d853049302bf5d104055c6f0cb7ef283537da3ce3f730073001"}, - {file = "google_cloud_storage-1.44.0-py2.py3-none-any.whl", hash = "sha256:cd4a223e9c18d771721a85c98a9c01b97d257edddff833ba63b7b1f0b9b4d6e9"}, + {file = "google-cloud-storage-2.17.0.tar.gz", hash = "sha256:49378abff54ef656b52dca5ef0f2eba9aa83dc2b2c72c78714b03a1a95fe9388"}, + {file = "google_cloud_storage-2.17.0-py2.py3-none-any.whl", hash = "sha256:5b393bc766b7a3bc6f5407b9e665b2450d36282614b7945e570b3480a456d1e1"}, ] [package.dependencies] -google-api-core = {version = ">=1.29.0,<3.0dev", markers = "python_version >= \"3.6\""} -google-auth = {version = ">=1.25.0,<3.0dev", markers = "python_version >= \"3.6\""} -google-cloud-core = {version = ">=1.6.0,<3.0dev", markers = "python_version >= \"3.6\""} -google-resumable-media = {version = ">=1.3.0,<3.0dev", markers = "python_version >= \"3.6\""} -protobuf = {version = "*", markers = "python_version >= \"3.6\""} +google-api-core = ">=2.15.0,<3.0.0dev" +google-auth = ">=2.26.1,<3.0dev" +google-cloud-core = ">=2.3.0,<3.0dev" +google-crc32c = ">=1.0,<2.0dev" +google-resumable-media = ">=2.6.0" requests = ">=2.18.0,<3.0.0dev" -six = "*" + +[package.extras] +protobuf = ["protobuf (<5.0.0dev)"] [[package]] name = "google-crc32c" version = "1.5.0" description = "A python wrapper of the C library 'Google CRC32C'" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1248,120 +1203,114 @@ testing = ["pytest"] [[package]] name = "google-resumable-media" -version = "2.5.0" +version = "2.7.1" description = "Utilities for Google Media Downloads and Resumable Uploads" -category = "main" optional = false -python-versions = ">= 3.7" +python-versions = ">=3.7" files = [ - {file = "google-resumable-media-2.5.0.tar.gz", hash = "sha256:218931e8e2b2a73a58eb354a288e03a0fd5fb1c4583261ac6e4c078666468c93"}, - {file = "google_resumable_media-2.5.0-py2.py3-none-any.whl", hash = "sha256:da1bd943e2e114a56d85d6848497ebf9be6a14d3db23e9fc57581e7c3e8170ec"}, + {file = "google-resumable-media-2.7.1.tar.gz", hash = "sha256:eae451a7b2e2cdbaaa0fd2eb00cc8a1ee5e95e16b55597359cbc3d27d7d90e33"}, + {file = "google_resumable_media-2.7.1-py2.py3-none-any.whl", hash = "sha256:103ebc4ba331ab1bfdac0250f8033627a2cd7cde09e7ccff9181e31ba4315b2c"}, ] [package.dependencies] google-crc32c = ">=1.0,<2.0dev" [package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)"] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "google-auth (>=1.22.0,<2.0dev)"] requests = ["requests (>=2.18.0,<3.0.0dev)"] [[package]] name = "googleapis-common-protos" -version = "1.60.0" +version = "1.63.1" description = "Common protobufs used in Google APIs" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis-common-protos-1.60.0.tar.gz", hash = "sha256:e73ebb404098db405ba95d1e1ae0aa91c3e15a71da031a2eeb6b2e23e7bc3708"}, - {file = "googleapis_common_protos-1.60.0-py2.py3-none-any.whl", hash = "sha256:69f9bbcc6acde92cab2db95ce30a70bd2b81d20b12eff3f1aabaffcbe8a93918"}, + {file = "googleapis-common-protos-1.63.1.tar.gz", hash = "sha256:c6442f7a0a6b2a80369457d79e6672bb7dcbaab88e0848302497e3ec80780a6a"}, + {file = "googleapis_common_protos-1.63.1-py2.py3-none-any.whl", hash = "sha256:0e1c2cdfcbc354b76e4a211a35ea35d6926a835cba1377073c4861db904a1877"}, ] [package.dependencies] -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" [package.extras] grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] [[package]] name = "greenlet" -version = "2.0.2" +version = "3.0.3" description = "Lightweight in-process concurrent programming" -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -files = [ - {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, - {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, - {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, - {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, - {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, - {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, - {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, - {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, - {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, - {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, - {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, - {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, - {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, - {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, - {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, - {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, - {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, - {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, - {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, - {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, - {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, - {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, - {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, - {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, - {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, - {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, ] [package.extras] -docs = ["Sphinx", "docutils (<0.18)"] +docs = ["Sphinx", "furo"] test = ["objgraph", "psutil"] [[package]] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1371,31 +1320,29 @@ files = [ [[package]] name = "httpcore" -version = "0.17.3" +version = "1.0.5" description = "A minimal low-level HTTP client." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"}, - {file = "httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"}, + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, ] [package.dependencies] -anyio = ">=3.0,<5.0" certifi = "*" h11 = ">=0.13,<0.15" -sniffio = ">=1.0.0,<2.0.0" [package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.26.0)"] [[package]] name = "httplib2" version = "0.22.0" description = "A comprehensive HTTP client library." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1408,33 +1355,32 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "httpx" -version = "0.24.1" +version = "0.27.0" description = "The next generation HTTP client." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "httpx-0.24.1-py3-none-any.whl", hash = "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd"}, - {file = "httpx-0.24.1.tar.gz", hash = "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] +anyio = "*" certifi = "*" -httpcore = ">=0.15.0,<0.18.0" +httpcore = "==1.*" idna = "*" sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "idna" version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1444,29 +1390,27 @@ files = [ [[package]] name = "importlib-metadata" -version = "6.8.0" +version = "7.1.0" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, + {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, + {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "isodate" version = "0.6.1" description = "An ISO 8601 date/time/duration parser and formatter" -category = "main" optional = false python-versions = "*" files = [ @@ -1479,26 +1423,24 @@ six = "*" [[package]] name = "itsdangerous" -version = "2.1.2" +version = "2.2.0" description = "Safely pass data to untrusted environments and back." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, - {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, ] [[package]] name = "jinja2" -version = "3.1.2" +version = "3.1.4" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -1509,21 +1451,19 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "jmespath" -version = "0.9.2" +version = "1.0.1" description = "JSON Matching Expressions" -category = "main" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "jmespath-0.9.2-py2.py3-none-any.whl", hash = "sha256:3f03b90ac8e0f3ba472e8ebff083e460c89501d8d41979771535efe9a343177e"}, - {file = "jmespath-0.9.2.tar.gz", hash = "sha256:54c441e2e08b23f12d7fa7d8e6761768c47c969e6aed10eead57505ba760aee9"}, + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, ] [[package]] name = "jsondiff" version = "1.1.1" description = "Diff JSON and JSON-like structures in Python" -category = "dev" optional = false python-versions = "*" files = [ @@ -1532,31 +1472,29 @@ files = [ [[package]] name = "jsonpickle" -version = "3.0.1" -description = "Python library for serializing any arbitrary object graph into JSON" -category = "dev" +version = "3.2.1" +description = "Python library for serializing arbitrary object graphs into JSON" optional = false python-versions = ">=3.7" files = [ - {file = "jsonpickle-3.0.1-py2.py3-none-any.whl", hash = "sha256:130d8b293ea0add3845de311aaba55e6d706d0bb17bc123bd2c8baf8a39ac77c"}, - {file = "jsonpickle-3.0.1.tar.gz", hash = "sha256:032538804795e73b94ead410800ac387fdb6de98f8882ac957fcd247e3a85200"}, + {file = "jsonpickle-3.2.1-py3-none-any.whl", hash = "sha256:ec291e4719674dd35d390fbdb521ac6517fbe9f541d361c8bffc8131133b1661"}, + {file = "jsonpickle-3.2.1.tar.gz", hash = "sha256:4b6d7640974199f7acf9035295365b5a1a71a91109effa15ba170fbb48cf871c"}, ] [package.extras] -docs = ["jaraco.packaging (>=3.2)", "rst.linker (>=1.9)", "sphinx"] -testing = ["ecdsa", "feedparser", "gmpy2", "numpy", "pandas", "pymongo", "pytest (>=3.5,!=3.7.3)", "pytest-black-multipy", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-flake8 (>=1.1.1)", "scikit-learn", "sqlalchemy"] -testing-libs = ["simplejson", "ujson"] +docs = ["furo", "rst.linker (>=1.9)", "sphinx"] +packaging = ["build", "twine"] +testing = ["bson", "ecdsa", "feedparser", "gmpy2", "numpy", "pandas", "pymongo", "pytest (>=3.5,!=3.7.3)", "pytest-benchmark", "pytest-benchmark[histogram]", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-ruff (>=0.2.1)", "scikit-learn", "scipy", "scipy (>=1.9.3)", "simplejson", "sqlalchemy", "ujson"] [[package]] name = "mako" -version = "1.2.4" +version = "1.3.5" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Mako-1.2.4-py3-none-any.whl", hash = "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818"}, - {file = "Mako-1.2.4.tar.gz", hash = "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34"}, + {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, + {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, ] [package.dependencies] @@ -1569,88 +1507,95 @@ testing = ["pytest"] [[package]] name = "markdown" -version = "3.4.4" +version = "3.6" description = "Python implementation of John Gruber's Markdown." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"}, - {file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"}, + {file = "Markdown-3.6-py3-none-any.whl", hash = "sha256:48f276f4d8cfb8ce6527c8f79e2ee29708508bf4d40aa410fbc3b4ee832c850f"}, + {file = "Markdown-3.6.tar.gz", hash = "sha256:ed4f41f6daecbeeb96e576ce414c41d2d876daa9a16cb35fa8ed8c2ddfad0224"}, ] [package.dependencies] importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} [package.extras] -docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.0)", "mkdocs-nature (>=0.4)"] +docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] testing = ["coverage", "pyyaml"] [[package]] name = "markupsafe" -version = "2.1.3" +version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] name = "mock" version = "2.0.0" description = "Rolling backport of unittest.mock for all Pythons" -category = "dev" optional = false python-versions = "*" files = [ @@ -1668,21 +1613,19 @@ test = ["unittest2 (>=1.1.0)"] [[package]] name = "more-itertools" -version = "10.1.0" +version = "10.3.0" description = "More routines for operating on iterables, beyond itertools" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "more-itertools-10.1.0.tar.gz", hash = "sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a"}, - {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"}, + {file = "more-itertools-10.3.0.tar.gz", hash = "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463"}, + {file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"}, ] [[package]] name = "moto" version = "1.3.7" description = "A library that allows your python tests to easily mock out the boto library" -category = "dev" optional = false python-versions = "*" files = [ @@ -1715,13 +1658,13 @@ server = ["flask"] [[package]] name = "oauth2client" -version = "3.0.0" +version = "4.1.3" description = "OAuth 2.0 client library" -category = "main" optional = false python-versions = "*" files = [ - {file = "oauth2client-3.0.0.tar.gz", hash = "sha256:5b5b056ec6f2304e7920b632885bd157fa71d1a7f3ddd00a43b1541a8d1a2460"}, + {file = "oauth2client-4.1.3-py2.py3-none-any.whl", hash = "sha256:b8a81cc5d60e2d364f0b1b98f958dbd472887acaf1a5b05e21c28c31a2d6d3ac"}, + {file = "oauth2client-4.1.3.tar.gz", hash = "sha256:d486741e451287f69568a4d26d70d9acd73a2bbfa275746c535b4209891cccc6"}, ] [package.dependencies] @@ -1731,63 +1674,67 @@ pyasn1-modules = ">=0.0.5" rsa = ">=3.1.4" six = ">=1.6.1" +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + [[package]] name = "paramiko" -version = "2.12.0" +version = "3.4.0" description = "SSH2 protocol library" -category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "paramiko-2.12.0-py2.py3-none-any.whl", hash = "sha256:b2df1a6325f6996ef55a8789d0462f5b502ea83b3c990cbb5bbe57345c6812c4"}, - {file = "paramiko-2.12.0.tar.gz", hash = "sha256:376885c05c5d6aa6e1f4608aac2a6b5b0548b1add40274477324605903d9cd49"}, + {file = "paramiko-3.4.0-py3-none-any.whl", hash = "sha256:43f0b51115a896f9c00f59618023484cb3a14b98bbceab43394a39c6739b7ee7"}, + {file = "paramiko-3.4.0.tar.gz", hash = "sha256:aac08f26a31dc4dffd92821527d1682d99d52f9ef6851968114a8728f3c274d3"}, ] [package.dependencies] -bcrypt = ">=3.1.3" -cryptography = ">=2.5" -pynacl = ">=1.0.1" -six = "*" +bcrypt = ">=3.2" +cryptography = ">=3.3" +pynacl = ">=1.5" [package.extras] -all = ["bcrypt (>=3.1.3)", "gssapi (>=1.4.1)", "invoke (>=1.3)", "pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "pywin32 (>=2.1.8)"] -ed25519 = ["bcrypt (>=3.1.3)", "pynacl (>=1.0.1)"] +all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] -invoke = ["invoke (>=1.3)"] +invoke = ["invoke (>=2.0)"] [[package]] name = "pbr" -version = "2.0.0" +version = "6.0.0" description = "Python Build Reasonableness" -category = "main" optional = false -python-versions = "*" +python-versions = ">=2.6" files = [ - {file = "pbr-2.0.0-py2.py3-none-any.whl", hash = "sha256:d9b69a26a5cb4e3898eb3c5cea54d2ab3332382167f04e30db5e1f54e1945e45"}, - {file = "pbr-2.0.0.tar.gz", hash = "sha256:0ccd2db529afd070df815b1521f01401d43de03941170f8a800e7531faba265d"}, + {file = "pbr-6.0.0-py2.py3-none-any.whl", hash = "sha256:4a7317d5e3b17a3dccb6a8cfe67dab65b20551404c52c8ed41279fa4f0cb4cda"}, + {file = "pbr-6.0.0.tar.gz", hash = "sha256:d1377122a5a00e2f940ee482999518efe16d745d423a670c27773dfbc3c9a7d9"}, ] [[package]] name = "pluggy" -version = "1.2.0" +version = "0.13.1" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] [[package]] name = "prometheus-client" version = "0.9.0" description = "Python client for the Prometheus monitoring system." -category = "main" optional = false python-versions = "*" files = [ @@ -1798,66 +1745,67 @@ files = [ [package.extras] twisted = ["twisted"] +[[package]] +name = "proto-plus" +version = "1.23.0" +description = "Beautiful, Pythonic protocol buffers." +optional = false +python-versions = ">=3.6" +files = [ + {file = "proto-plus-1.23.0.tar.gz", hash = "sha256:89075171ef11988b3fa157f5dbd8b9cf09d65fffee97e29ce403cd8defba19d2"}, + {file = "proto_plus-1.23.0-py3-none-any.whl", hash = "sha256:a829c79e619e1cf632de091013a4173deed13a55f326ef84f05af6f50ff4c82c"}, +] + +[package.dependencies] +protobuf = ">=3.19.0,<5.0.0dev" + +[package.extras] +testing = ["google-api-core[grpc] (>=1.31.5)"] + [[package]] name = "protobuf" -version = "3.20.3" -description = "Protocol Buffers" -category = "main" +version = "4.25.3" +description = "" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99"}, - {file = "protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e"}, - {file = "protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c"}, - {file = "protobuf-3.20.3-cp310-cp310-win_amd64.whl", hash = "sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7"}, - {file = "protobuf-3.20.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469"}, - {file = "protobuf-3.20.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4"}, - {file = "protobuf-3.20.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4"}, - {file = "protobuf-3.20.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454"}, - {file = "protobuf-3.20.3-cp37-cp37m-win32.whl", hash = "sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905"}, - {file = "protobuf-3.20.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c"}, - {file = "protobuf-3.20.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7"}, - {file = "protobuf-3.20.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee"}, - {file = "protobuf-3.20.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050"}, - {file = "protobuf-3.20.3-cp38-cp38-win32.whl", hash = "sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86"}, - {file = "protobuf-3.20.3-cp38-cp38-win_amd64.whl", hash = "sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9"}, - {file = "protobuf-3.20.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b"}, - {file = "protobuf-3.20.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b"}, - {file = "protobuf-3.20.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402"}, - {file = "protobuf-3.20.3-cp39-cp39-win32.whl", hash = "sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480"}, - {file = "protobuf-3.20.3-cp39-cp39-win_amd64.whl", hash = "sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7"}, - {file = "protobuf-3.20.3-py2.py3-none-any.whl", hash = "sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db"}, - {file = "protobuf-3.20.3.tar.gz", hash = "sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2"}, + {file = "protobuf-4.25.3-cp310-abi3-win32.whl", hash = "sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa"}, + {file = "protobuf-4.25.3-cp310-abi3-win_amd64.whl", hash = "sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8"}, + {file = "protobuf-4.25.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c"}, + {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019"}, + {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d"}, + {file = "protobuf-4.25.3-cp38-cp38-win32.whl", hash = "sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2"}, + {file = "protobuf-4.25.3-cp38-cp38-win_amd64.whl", hash = "sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4"}, + {file = "protobuf-4.25.3-cp39-cp39-win32.whl", hash = "sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4"}, + {file = "protobuf-4.25.3-cp39-cp39-win_amd64.whl", hash = "sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c"}, + {file = "protobuf-4.25.3-py3-none-any.whl", hash = "sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9"}, + {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"}, ] [[package]] name = "psycopg2" -version = "2.9.6" +version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "psycopg2-2.9.6-cp310-cp310-win32.whl", hash = "sha256:f7a7a5ee78ba7dc74265ba69e010ae89dae635eea0e97b055fb641a01a31d2b1"}, - {file = "psycopg2-2.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:f75001a1cbbe523e00b0ef896a5a1ada2da93ccd752b7636db5a99bc57c44494"}, - {file = "psycopg2-2.9.6-cp311-cp311-win32.whl", hash = "sha256:53f4ad0a3988f983e9b49a5d9765d663bbe84f508ed655affdb810af9d0972ad"}, - {file = "psycopg2-2.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:b81fcb9ecfc584f661b71c889edeae70bae30d3ef74fa0ca388ecda50b1222b7"}, - {file = "psycopg2-2.9.6-cp36-cp36m-win32.whl", hash = "sha256:11aca705ec888e4f4cea97289a0bf0f22a067a32614f6ef64fcf7b8bfbc53744"}, - {file = "psycopg2-2.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:36c941a767341d11549c0fbdbb2bf5be2eda4caf87f65dfcd7d146828bd27f39"}, - {file = "psycopg2-2.9.6-cp37-cp37m-win32.whl", hash = "sha256:869776630c04f335d4124f120b7fb377fe44b0a7645ab3c34b4ba42516951889"}, - {file = "psycopg2-2.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:a8ad4a47f42aa6aec8d061fdae21eaed8d864d4bb0f0cade5ad32ca16fcd6258"}, - {file = "psycopg2-2.9.6-cp38-cp38-win32.whl", hash = "sha256:2362ee4d07ac85ff0ad93e22c693d0f37ff63e28f0615a16b6635a645f4b9214"}, - {file = "psycopg2-2.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:d24ead3716a7d093b90b27b3d73459fe8cd90fd7065cf43b3c40966221d8c394"}, - {file = "psycopg2-2.9.6-cp39-cp39-win32.whl", hash = "sha256:1861a53a6a0fd248e42ea37c957d36950da00266378746588eab4f4b5649e95f"}, - {file = "psycopg2-2.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:ded2faa2e6dfb430af7713d87ab4abbfc764d8d7fb73eafe96a24155f906ebf5"}, - {file = "psycopg2-2.9.6.tar.gz", hash = "sha256:f15158418fd826831b28585e2ab48ed8df2d0d98f502a2b4fe619e7d5ca29011"}, + {file = "psycopg2-2.9.9-cp310-cp310-win32.whl", hash = "sha256:38a8dcc6856f569068b47de286b472b7c473ac7977243593a288ebce0dc89516"}, + {file = "psycopg2-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3"}, + {file = "psycopg2-2.9.9-cp311-cp311-win32.whl", hash = "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372"}, + {file = "psycopg2-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981"}, + {file = "psycopg2-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa"}, + {file = "psycopg2-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a"}, + {file = "psycopg2-2.9.9-cp38-cp38-win32.whl", hash = "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c"}, + {file = "psycopg2-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:bac58c024c9922c23550af2a581998624d6e02350f4ae9c5f0bc642c633a2d5e"}, + {file = "psycopg2-2.9.9-cp39-cp39-win32.whl", hash = "sha256:c92811b2d4c9b6ea0285942b2e7cac98a59e166d59c588fe5cfe1eda58e72d59"}, + {file = "psycopg2-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:de80739447af31525feddeb8effd640782cf5998e1a4e9192ebdf829717e3913"}, + {file = "psycopg2-2.9.9.tar.gz", hash = "sha256:d1454bde93fb1e224166811694d600e746430c006fbb031ea06ecc2ea41bf156"}, ] [[package]] name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1867,14 +1815,13 @@ files = [ [[package]] name = "pyaml" -version = "23.7.0" +version = "24.4.0" description = "PyYAML-based module to produce a bit more pretty and readable YAML-serialized data" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pyaml-23.7.0-py3-none-any.whl", hash = "sha256:0a37018282545ccc31faecbe138fda4d89e236af04d691cfb5af00cd60089345"}, - {file = "pyaml-23.7.0.tar.gz", hash = "sha256:0c510bbb8938309400e0b1e47ac16fd90e56d652805a93417128786718f33546"}, + {file = "pyaml-24.4.0-py3-none-any.whl", hash = "sha256:acc2b39c55cb0cbe4f694a6d3886f89ad3d2a5b3efcece526202f8de9a6b27de"}, + {file = "pyaml-24.4.0.tar.gz", hash = "sha256:0e483d9289010e747a325dc43171bcc39d6562dd1dd4719e8cc7e7c96c99fce6"}, ] [package.dependencies] @@ -1885,90 +1832,85 @@ anchors = ["unidecode"] [[package]] name = "pyasn1" -version = "0.5.0" +version = "0.6.0" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.8" files = [ - {file = "pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"}, - {file = "pyasn1-0.5.0.tar.gz", hash = "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde"}, + {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, + {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, ] [[package]] name = "pyasn1-modules" -version = "0.3.0" +version = "0.4.0" description = "A collection of ASN.1-based protocols modules" -category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.8" files = [ - {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, - {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, + {file = "pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"}, + {file = "pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6"}, ] [package.dependencies] -pyasn1 = ">=0.4.6,<0.6.0" +pyasn1 = ">=0.4.6,<0.7.0" [[package]] name = "pycparser" -version = "2.21" +version = "2.22" description = "C parser in Python" -category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] [[package]] name = "pycryptodome" -version = "3.18.0" +version = "3.20.0" description = "Cryptographic library for Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pycryptodome-3.18.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:d1497a8cd4728db0e0da3c304856cb37c0c4e3d0b36fcbabcc1600f18504fc54"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:928078c530da78ff08e10eb6cada6e0dff386bf3d9fa9871b4bbc9fbc1efe024"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:157c9b5ba5e21b375f052ca78152dd309a09ed04703fd3721dce3ff8ecced148"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:d20082bdac9218649f6abe0b885927be25a917e29ae0502eaf2b53f1233ce0c2"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e8ad74044e5f5d2456c11ed4cfd3e34b8d4898c0cb201c4038fe41458a82ea27"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-win32.whl", hash = "sha256:62a1e8847fabb5213ccde38915563140a5b338f0d0a0d363f996b51e4a6165cf"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-win_amd64.whl", hash = "sha256:16bfd98dbe472c263ed2821284118d899c76968db1a6665ade0c46805e6b29a4"}, - {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7a3d22c8ee63de22336679e021c7f2386f7fc465477d59675caa0e5706387944"}, - {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:78d863476e6bad2a592645072cc489bb90320972115d8995bcfbee2f8b209918"}, - {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:b6a610f8bfe67eab980d6236fdc73bfcdae23c9ed5548192bb2d530e8a92780e"}, - {file = "pycryptodome-3.18.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:422c89fd8df8a3bee09fb8d52aaa1e996120eafa565437392b781abec2a56e14"}, - {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:9ad6f09f670c466aac94a40798e0e8d1ef2aa04589c29faa5b9b97566611d1d1"}, - {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:53aee6be8b9b6da25ccd9028caf17dcdce3604f2c7862f5167777b707fbfb6cb"}, - {file = "pycryptodome-3.18.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:10da29526a2a927c7d64b8f34592f461d92ae55fc97981aab5bbcde8cb465bb6"}, - {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f21efb8438971aa16924790e1c3dba3a33164eb4000106a55baaed522c261acf"}, - {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4944defabe2ace4803f99543445c27dd1edbe86d7d4edb87b256476a91e9ffa4"}, - {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:51eae079ddb9c5f10376b4131be9589a6554f6fd84f7f655180937f611cd99a2"}, - {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:83c75952dcf4a4cebaa850fa257d7a860644c70a7cd54262c237c9f2be26f76e"}, - {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:957b221d062d5752716923d14e0926f47670e95fead9d240fa4d4862214b9b2f"}, - {file = "pycryptodome-3.18.0-cp35-abi3-win32.whl", hash = "sha256:795bd1e4258a2c689c0b1f13ce9684fa0dd4c0e08680dcf597cf9516ed6bc0f3"}, - {file = "pycryptodome-3.18.0-cp35-abi3-win_amd64.whl", hash = "sha256:b1d9701d10303eec8d0bd33fa54d44e67b8be74ab449052a8372f12a66f93fb9"}, - {file = "pycryptodome-3.18.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:cb1be4d5af7f355e7d41d36d8eec156ef1382a88638e8032215c215b82a4b8ec"}, - {file = "pycryptodome-3.18.0-pp27-pypy_73-win32.whl", hash = "sha256:fc0a73f4db1e31d4a6d71b672a48f3af458f548059aa05e83022d5f61aac9c08"}, - {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f022a4fd2a5263a5c483a2bb165f9cb27f2be06f2f477113783efe3fe2ad887b"}, - {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:363dd6f21f848301c2dcdeb3c8ae5f0dee2286a5e952a0f04954b82076f23825"}, - {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12600268763e6fec3cefe4c2dcdf79bde08d0b6dc1813887e789e495cb9f3403"}, - {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4604816adebd4faf8810782f137f8426bf45fee97d8427fa8e1e49ea78a52e2c"}, - {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:01489bbdf709d993f3058e2996f8f40fee3f0ea4d995002e5968965fa2fe89fb"}, - {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3811e31e1ac3069988f7a1c9ee7331b942e605dfc0f27330a9ea5997e965efb2"}, - {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4b967bb11baea9128ec88c3d02f55a3e338361f5e4934f5240afcb667fdaec"}, - {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9c8eda4f260072f7dbe42f473906c659dcbadd5ae6159dfb49af4da1293ae380"}, - {file = "pycryptodome-3.18.0.tar.gz", hash = "sha256:c9adee653fc882d98956e33ca2c1fb582e23a8af7ac82fee75bd6113c55a0413"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win32.whl", hash = "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e"}, + {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, ] [[package]] name = "pyjwt" version = "2.8.0" description = "JSON Web Token implementation in Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1989,7 +1931,6 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] name = "pynacl" version = "1.5.0" description = "Python binding to the Networking and Cryptography (NaCl) library" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2014,14 +1955,13 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "pyparsing" -version = "3.1.1" +version = "3.1.2" description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" optional = false python-versions = ">=3.6.8" files = [ - {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, - {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, + {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, + {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, ] [package.extras] @@ -2029,75 +1969,76 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "3.10.1" +version = "5.4.3" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" files = [ - {file = "pytest-3.10.1-py2.py3-none-any.whl", hash = "sha256:3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec"}, - {file = "pytest-3.10.1.tar.gz", hash = "sha256:e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"}, + {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, + {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, ] [package.dependencies] -atomicwrites = ">=1.0" +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=17.4.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} more-itertools = ">=4.0.0" -pluggy = ">=0.7" +packaging = "*" +pluggy = ">=0.12,<1.0" py = ">=1.5.0" -setuptools = "*" -six = ">=1.10.0" +wcwidth = "*" + +[package.extras] +checkqa-mypy = ["mypy (==v0.761)"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] name = "pytest-cov" -version = "2.9.0" +version = "2.12.1" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pytest-cov-2.9.0.tar.gz", hash = "sha256:b6a814b8ed6247bd81ff47f038511b57fe1ce7f4cc25b9106f1a4b106f1d9322"}, - {file = "pytest_cov-2.9.0-py2.py3-none-any.whl", hash = "sha256:c87dfd8465d865655a8213859f1b4749b43448b5fae465cb981e16d52a811424"}, + {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, + {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, ] [package.dependencies] -coverage = ">=4.4" -pytest = ">=3.6" +coverage = ">=5.2.1" +pytest = ">=4.6" +toml = "*" [package.extras] -testing = ["fields", "hunter", "process-tests (==2.0.2)", "pytest-xdist", "six", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "pytest-flask" -version = "0.15.1" +version = "1.3.0" description = "A set of py.test fixtures to test Flask applications." -category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "pytest-flask-0.15.1.tar.gz", hash = "sha256:cbd8c5b9f8f1b83e9c159ac4294964807c4934317a5fba181739ac15e1b823e6"}, - {file = "pytest_flask-0.15.1-py2.py3-none-any.whl", hash = "sha256:9001f6128c5c4a0d243ce46c117f3691052828d2faf39ac151b8388657dce447"}, + {file = "pytest-flask-1.3.0.tar.gz", hash = "sha256:58be1c97b21ba3c4d47e0a7691eb41007748506c36bf51004f78df10691fa95e"}, + {file = "pytest_flask-1.3.0-py3-none-any.whl", hash = "sha256:c0e36e6b0fddc3b91c4362661db83fa694d1feb91fa505475be6732b5bc8c253"}, ] [package.dependencies] Flask = "*" -pytest = ">=3.6" -Werkzeug = ">=0.7" +pytest = ">=5.2" +Werkzeug = "*" [package.extras] docs = ["Sphinx", "sphinx-rtd-theme"] [[package]] name = "python-dateutil" -version = "2.8.2" +version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {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"}, + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [package.dependencies] @@ -2107,7 +2048,6 @@ six = ">=1.5" name = "python-jose" version = "2.0.2" description = "JOSE implementation in Python" -category = "main" optional = false python-versions = "*" files = [ @@ -2127,87 +2067,106 @@ pycrypto = ["pycrypto (>=2.6.0,<2.7.0)"] [[package]] name = "pytz" -version = "2023.3" +version = "2024.1" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, - {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] [[package]] name = "pywin32" -version = "227" +version = "306" description = "Python for Window Extensions" -category = "dev" optional = false python-versions = "*" files = [ - {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, - {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, - {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, - {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, - {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, - {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, - {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, - {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, - {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, - {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, - {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, - {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, ] [[package]] name = "pyyaml" -version = "5.4.1" +version = "6.0.1" description = "YAML parser and emitter for Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -files = [ - {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, - {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, - {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, - {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, - {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, - {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, - {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, - {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, - {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, - {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, - {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -2222,30 +2181,27 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "responses" -version = "0.23.3" +version = "0.25.3" description = "A utility library for mocking out the `requests` Python library." -category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "responses-0.23.3-py3-none-any.whl", hash = "sha256:e6fbcf5d82172fecc0aa1860fd91e58cbfd96cee5e96da5b63fa6eb3caa10dd3"}, - {file = "responses-0.23.3.tar.gz", hash = "sha256:205029e1cb334c21cb4ec64fc7599be48b859a0fd381a42443cdd600bfe8b16a"}, + {file = "responses-0.25.3-py3-none-any.whl", hash = "sha256:521efcbc82081ab8daa588e08f7e8a64ce79b91c39f6e62199b19159bea7dbcb"}, + {file = "responses-0.25.3.tar.gz", hash = "sha256:617b9247abd9ae28313d57a75880422d55ec63c29d33d629697590a034358dba"}, ] [package.dependencies] pyyaml = "*" requests = ">=2.30.0,<3.0" -types-PyYAML = "*" urllib3 = ">=1.25.10,<3.0" [package.extras] -tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-requests"] +tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"] [[package]] name = "retry" version = "0.9.2" description = "Easy to use retry decorator." -category = "main" optional = false python-versions = "*" files = [ @@ -2261,7 +2217,6 @@ py = ">=1.4.26,<2.0.0" name = "rsa" version = "4.9" description = "Pure-Python RSA implementation" -category = "main" optional = false python-versions = ">=3.6,<4" files = [ @@ -2274,41 +2229,25 @@ pyasn1 = ">=0.1.3" [[package]] name = "s3transfer" -version = "0.2.1" +version = "0.10.1" description = "An Amazon S3 Transfer Manager" -category = "main" optional = false -python-versions = "*" +python-versions = ">= 3.8" files = [ - {file = "s3transfer-0.2.1-py2.py3-none-any.whl", hash = "sha256:b780f2411b824cb541dbcd2c713d0cb61c7d1bcadae204cdddda2b35cef493ba"}, - {file = "s3transfer-0.2.1.tar.gz", hash = "sha256:6efc926738a3cd576c2a79725fed9afde92378aa5c6a957e3af010cb019fac9d"}, + {file = "s3transfer-0.10.1-py3-none-any.whl", hash = "sha256:ceb252b11bcf87080fb7850a224fb6e05c8a776bab8f2b64b7f25b969464839d"}, + {file = "s3transfer-0.10.1.tar.gz", hash = "sha256:5683916b4c724f799e600f41dd9e10a9ff19871bf87623cc8f491cb4f5fa0a19"}, ] [package.dependencies] -botocore = ">=1.12.36,<2.0.0" - -[[package]] -name = "setuptools" -version = "68.0.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, - {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, -] +botocore = ">=1.33.2,<2.0a.0" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -2318,70 +2257,76 @@ files = [ [[package]] name = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] name = "sqlalchemy" -version = "1.4.49" +version = "1.4.52" description = "Database Abstraction Library" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "SQLAlchemy-1.4.49-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e126cf98b7fd38f1e33c64484406b78e937b1a280e078ef558b95bf5b6895f6"}, - {file = "SQLAlchemy-1.4.49-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:03db81b89fe7ef3857b4a00b63dedd632d6183d4ea5a31c5d8a92e000a41fc71"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:95b9df9afd680b7a3b13b38adf6e3a38995da5e162cc7524ef08e3be4e5ed3e1"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63e43bf3f668c11bb0444ce6e809c1227b8f067ca1068898f3008a273f52b09"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f835c050ebaa4e48b18403bed2c0fda986525896efd76c245bdd4db995e51a4c"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c21b172dfb22e0db303ff6419451f0cac891d2e911bb9fbf8003d717f1bcf91"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-win32.whl", hash = "sha256:5fb1ebdfc8373b5a291485757bd6431de8d7ed42c27439f543c81f6c8febd729"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-win_amd64.whl", hash = "sha256:f8a65990c9c490f4651b5c02abccc9f113a7f56fa482031ac8cb88b70bc8ccaa"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8923dfdf24d5aa8a3adb59723f54118dd4fe62cf59ed0d0d65d940579c1170a4"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9ab2c507a7a439f13ca4499db6d3f50423d1d65dc9b5ed897e70941d9e135b0"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5debe7d49b8acf1f3035317e63d9ec8d5e4d904c6e75a2a9246a119f5f2fdf3d"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-win32.whl", hash = "sha256:82b08e82da3756765c2e75f327b9bf6b0f043c9c3925fb95fb51e1567fa4ee87"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-win_amd64.whl", hash = "sha256:171e04eeb5d1c0d96a544caf982621a1711d078dbc5c96f11d6469169bd003f1"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:36e58f8c4fe43984384e3fbe6341ac99b6b4e083de2fe838f0fdb91cebe9e9cb"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b31e67ff419013f99ad6f8fc73ee19ea31585e1e9fe773744c0f3ce58c039c30"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c14b29d9e1529f99efd550cd04dbb6db6ba5d690abb96d52de2bff4ed518bc95"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c40f3470e084d31247aea228aa1c39bbc0904c2b9ccbf5d3cfa2ea2dac06f26d"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-win32.whl", hash = "sha256:706bfa02157b97c136547c406f263e4c6274a7b061b3eb9742915dd774bbc264"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-win_amd64.whl", hash = "sha256:a7f7b5c07ae5c0cfd24c2db86071fb2a3d947da7bd487e359cc91e67ac1c6d2e"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:4afbbf5ef41ac18e02c8dc1f86c04b22b7a2125f2a030e25bbb4aff31abb224b"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24e300c0c2147484a002b175f4e1361f102e82c345bf263242f0449672a4bccf"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:201de072b818f8ad55c80d18d1a788729cccf9be6d9dc3b9d8613b053cd4836d"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653ed6817c710d0c95558232aba799307d14ae084cc9b1f4c389157ec50df5c"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-win32.whl", hash = "sha256:647e0b309cb4512b1f1b78471fdaf72921b6fa6e750b9f891e09c6e2f0e5326f"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-win_amd64.whl", hash = "sha256:ab73ed1a05ff539afc4a7f8cf371764cdf79768ecb7d2ec691e3ff89abbc541e"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:37ce517c011560d68f1ffb28af65d7e06f873f191eb3a73af5671e9c3fada08a"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1878ce508edea4a879015ab5215546c444233881301e97ca16fe251e89f1c55"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e8e608983e6f85d0852ca61f97e521b62e67969e6e640fe6c6b575d4db68557"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccf956da45290df6e809ea12c54c02ace7f8ff4d765d6d3dfb3655ee876ce58d"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-win32.whl", hash = "sha256:f167c8175ab908ce48bd6550679cc6ea20ae169379e73c7720a28f89e53aa532"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-win_amd64.whl", hash = "sha256:45806315aae81a0c202752558f0df52b42d11dd7ba0097bf71e253b4215f34f4"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:b6d0c4b15d65087738a6e22e0ff461b407533ff65a73b818089efc8eb2b3e1de"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a843e34abfd4c797018fd8d00ffffa99fd5184c421f190b6ca99def4087689bd"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c890421651b45a681181301b3497e4d57c0d01dc001e10438a40e9a9c25ee77"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d26f280b8f0a8f497bc10573849ad6dc62e671d2468826e5c748d04ed9e670d5"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-win32.whl", hash = "sha256:ec2268de67f73b43320383947e74700e95c6770d0c68c4e615e9897e46296294"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-win_amd64.whl", hash = "sha256:bbdf16372859b8ed3f4d05f925a984771cd2abd18bd187042f24be4886c2a15f"}, - {file = "SQLAlchemy-1.4.49.tar.gz", hash = "sha256:06ff25cbae30c396c4b7737464f2a7fc37a67b7da409993b182b024cec80aed9"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and platform_machine == \"aarch64\" or python_version >= \"3\" and platform_machine == \"ppc64le\" or python_version >= \"3\" and platform_machine == \"x86_64\" or python_version >= \"3\" and platform_machine == \"amd64\" or python_version >= \"3\" and platform_machine == \"AMD64\" or python_version >= \"3\" and platform_machine == \"win32\" or python_version >= \"3\" and platform_machine == \"WIN32\""} + {file = "SQLAlchemy-1.4.52-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:f68016f9a5713684c1507cc37133c28035f29925c75c0df2f9d0f7571e23720a"}, + {file = "SQLAlchemy-1.4.52-cp310-cp310-manylinux1_x86_64.manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24bb0f81fbbb13d737b7f76d1821ec0b117ce8cbb8ee5e8641ad2de41aa916d3"}, + {file = "SQLAlchemy-1.4.52-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e93983cc0d2edae253b3f2141b0a3fb07e41c76cd79c2ad743fc27eb79c3f6db"}, + {file = "SQLAlchemy-1.4.52-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:84e10772cfc333eb08d0b7ef808cd76e4a9a30a725fb62a0495877a57ee41d81"}, + {file = "SQLAlchemy-1.4.52-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:427988398d2902de042093d17f2b9619a5ebc605bf6372f7d70e29bde6736842"}, + {file = "SQLAlchemy-1.4.52-cp310-cp310-win32.whl", hash = "sha256:1296f2cdd6db09b98ceb3c93025f0da4835303b8ac46c15c2136e27ee4d18d94"}, + {file = "SQLAlchemy-1.4.52-cp310-cp310-win_amd64.whl", hash = "sha256:80e7f697bccc56ac6eac9e2df5c98b47de57e7006d2e46e1a3c17c546254f6ef"}, + {file = "SQLAlchemy-1.4.52-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2f251af4c75a675ea42766880ff430ac33291c8d0057acca79710f9e5a77383d"}, + {file = "SQLAlchemy-1.4.52-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8f9e4c4718f111d7b530c4e6fb4d28f9f110eb82e7961412955b3875b66de0"}, + {file = "SQLAlchemy-1.4.52-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afb1672b57f58c0318ad2cff80b384e816735ffc7e848d8aa51e0b0fc2f4b7bb"}, + {file = "SQLAlchemy-1.4.52-cp311-cp311-win32.whl", hash = "sha256:6e41cb5cda641f3754568d2ed8962f772a7f2b59403b95c60c89f3e0bd25f15e"}, + {file = "SQLAlchemy-1.4.52-cp311-cp311-win_amd64.whl", hash = "sha256:5bed4f8c3b69779de9d99eb03fd9ab67a850d74ab0243d1be9d4080e77b6af12"}, + {file = "SQLAlchemy-1.4.52-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:49e3772eb3380ac88d35495843daf3c03f094b713e66c7d017e322144a5c6b7c"}, + {file = "SQLAlchemy-1.4.52-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:618827c1a1c243d2540314c6e100aee7af09a709bd005bae971686fab6723554"}, + {file = "SQLAlchemy-1.4.52-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de9acf369aaadb71a725b7e83a5ef40ca3de1cf4cdc93fa847df6b12d3cd924b"}, + {file = "SQLAlchemy-1.4.52-cp312-cp312-win32.whl", hash = "sha256:763bd97c4ebc74136ecf3526b34808c58945023a59927b416acebcd68d1fc126"}, + {file = "SQLAlchemy-1.4.52-cp312-cp312-win_amd64.whl", hash = "sha256:f12aaf94f4d9679ca475975578739e12cc5b461172e04d66f7a3c39dd14ffc64"}, + {file = "SQLAlchemy-1.4.52-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:853fcfd1f54224ea7aabcf34b227d2b64a08cbac116ecf376907968b29b8e763"}, + {file = "SQLAlchemy-1.4.52-cp36-cp36m-manylinux1_x86_64.manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f98dbb8fcc6d1c03ae8ec735d3c62110949a3b8bc6e215053aa27096857afb45"}, + {file = "SQLAlchemy-1.4.52-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e135fff2e84103bc15c07edd8569612ce317d64bdb391f49ce57124a73f45c5"}, + {file = "SQLAlchemy-1.4.52-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b5de6af8852500d01398f5047d62ca3431d1e29a331d0b56c3e14cb03f8094c"}, + {file = "SQLAlchemy-1.4.52-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3491c85df263a5c2157c594f54a1a9c72265b75d3777e61ee13c556d9e43ffc9"}, + {file = "SQLAlchemy-1.4.52-cp36-cp36m-win32.whl", hash = "sha256:427c282dd0deba1f07bcbf499cbcc9fe9a626743f5d4989bfdfd3ed3513003dd"}, + {file = "SQLAlchemy-1.4.52-cp36-cp36m-win_amd64.whl", hash = "sha256:ca5ce82b11731492204cff8845c5e8ca1a4bd1ade85e3b8fcf86e7601bfc6a39"}, + {file = "SQLAlchemy-1.4.52-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:29d4247313abb2015f8979137fe65f4eaceead5247d39603cc4b4a610936cd2b"}, + {file = "SQLAlchemy-1.4.52-cp37-cp37m-manylinux1_x86_64.manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a752bff4796bf22803d052d4841ebc3c55c26fb65551f2c96e90ac7c62be763a"}, + {file = "SQLAlchemy-1.4.52-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7ea11727feb2861deaa293c7971a4df57ef1c90e42cb53f0da40c3468388000"}, + {file = "SQLAlchemy-1.4.52-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d913f8953e098ca931ad7f58797f91deed26b435ec3756478b75c608aa80d139"}, + {file = "SQLAlchemy-1.4.52-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a251146b921725547ea1735b060a11e1be705017b568c9f8067ca61e6ef85f20"}, + {file = "SQLAlchemy-1.4.52-cp37-cp37m-win32.whl", hash = "sha256:1f8e1c6a6b7f8e9407ad9afc0ea41c1f65225ce505b79bc0342159de9c890782"}, + {file = "SQLAlchemy-1.4.52-cp37-cp37m-win_amd64.whl", hash = "sha256:346ed50cb2c30f5d7a03d888e25744154ceac6f0e6e1ab3bc7b5b77138d37710"}, + {file = "SQLAlchemy-1.4.52-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:4dae6001457d4497736e3bc422165f107ecdd70b0d651fab7f731276e8b9e12d"}, + {file = "SQLAlchemy-1.4.52-cp38-cp38-manylinux1_x86_64.manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5d2e08d79f5bf250afb4a61426b41026e448da446b55e4770c2afdc1e200fce"}, + {file = "SQLAlchemy-1.4.52-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bbce5dd7c7735e01d24f5a60177f3e589078f83c8a29e124a6521b76d825b85"}, + {file = "SQLAlchemy-1.4.52-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bdb7b4d889631a3b2a81a3347c4c3f031812eb4adeaa3ee4e6b0d028ad1852b5"}, + {file = "SQLAlchemy-1.4.52-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c294ae4e6bbd060dd79e2bd5bba8b6274d08ffd65b58d106394cb6abbf35cf45"}, + {file = "SQLAlchemy-1.4.52-cp38-cp38-win32.whl", hash = "sha256:bcdfb4b47fe04967669874fb1ce782a006756fdbebe7263f6a000e1db969120e"}, + {file = "SQLAlchemy-1.4.52-cp38-cp38-win_amd64.whl", hash = "sha256:7d0dbc56cb6af5088f3658982d3d8c1d6a82691f31f7b0da682c7b98fa914e91"}, + {file = "SQLAlchemy-1.4.52-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:a551d5f3dc63f096ed41775ceec72fdf91462bb95abdc179010dc95a93957800"}, + {file = "SQLAlchemy-1.4.52-cp39-cp39-manylinux1_x86_64.manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ab773f9ad848118df7a9bbabca53e3f1002387cdbb6ee81693db808b82aaab0"}, + {file = "SQLAlchemy-1.4.52-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2de46f5d5396d5331127cfa71f837cca945f9a2b04f7cb5a01949cf676db7d1"}, + {file = "SQLAlchemy-1.4.52-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7027be7930a90d18a386b25ee8af30514c61f3852c7268899f23fdfbd3107181"}, + {file = "SQLAlchemy-1.4.52-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99224d621affbb3c1a4f72b631f8393045f4ce647dd3262f12fe3576918f8bf3"}, + {file = "SQLAlchemy-1.4.52-cp39-cp39-win32.whl", hash = "sha256:c124912fd4e1bb9d1e7dc193ed482a9f812769cb1e69363ab68e01801e859821"}, + {file = "SQLAlchemy-1.4.52-cp39-cp39-win_amd64.whl", hash = "sha256:2c286fab42e49db23c46ab02479f328b8bdb837d3e281cae546cc4085c83b680"}, + {file = "SQLAlchemy-1.4.52.tar.gz", hash = "sha256:80e63bbdc5217dad3485059bdf6f65a7d43f33c8bde619df5c220edf03d87296"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} [package.extras] -aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] @@ -2391,101 +2336,85 @@ mssql-pyodbc = ["pyodbc"] mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"] +oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"] postgresql = ["psycopg2 (>=2.7)"] postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] pymysql = ["pymysql", "pymysql (<1)"] -sqlcipher = ["sqlcipher3-binary"] +sqlcipher = ["sqlcipher3_binary"] [[package]] -name = "storageclient" -version = "0.1.0" -description = "" -category = "main" +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" optional = false -python-versions = "*" -files = [] -develop = false - -[package.dependencies] -boto = ">=2.36.0,<3.0.0" -botocore = ">=1.7,<1.13.0" -cdislogging = ">=1.0.0,<2.0.0" -gen3cirrus = ">=1.0.0,<3.0.0" -jmespath = "0.9.2" -pbr = "2.0.0" -requests = ">=2.5.2,<3.0.0" -s3transfer = ">=0.2.0,<0.3.0" -six = ">=1.13.0" -urllib3 = ">=1.20,<1.26" - -[package.source] -type = "git" -url = "https://github.com/uc-cdis/storage-client" -reference = "1.0.2" -resolved_reference = "4d39265d6e478acd5e1afe6e5dc722418f887d78" - -[[package]] -name = "types-pyyaml" -version = "6.0.12.11" -description = "Typing stubs for PyYAML" -category = "dev" -optional = false -python-versions = "*" +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ - {file = "types-PyYAML-6.0.12.11.tar.gz", hash = "sha256:7d340b19ca28cddfdba438ee638cd4084bde213e501a3978738543e27094775b"}, - {file = "types_PyYAML-6.0.12.11-py3-none-any.whl", hash = "sha256:a461508f3096d1d5810ec5ab95d7eeecb651f3a15b71959999988942063bf01d"}, + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] name = "uritemplate" -version = "3.0.1" -description = "URI templates" -category = "main" +version = "4.1.1" +description = "Implementation of RFC 6570 URI Templates" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" files = [ - {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, - {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, + {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, + {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, ] [[package]] name = "urllib3" -version = "1.25.11" +version = "1.26.19" 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.*, <4" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "urllib3-1.25.11-py2.py3-none-any.whl", hash = "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"}, - {file = "urllib3-1.25.11.tar.gz", hash = "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2"}, + {file = "urllib3-1.26.19-py2.py3-none-any.whl", hash = "sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3"}, + {file = "urllib3-1.26.19.tar.gz", hash = "sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429"}, ] [package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] +brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + [[package]] name = "userdatamodel" version = "2.4.3" description = "" -category = "main" optional = false python-versions = "*" files = [ @@ -2497,32 +2426,25 @@ cdislogging = "*" sqlalchemy = ">=1.3.3" [[package]] -name = "websocket-client" -version = "1.6.1" -description = "WebSocket client for Python with low level API options" -category = "dev" +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" optional = false -python-versions = ">=3.7" +python-versions = "*" files = [ - {file = "websocket-client-1.6.1.tar.gz", hash = "sha256:c951af98631d24f8df89ab1019fc365f2227c0892f12fd150e935607c79dd0dd"}, - {file = "websocket_client-1.6.1-py3-none-any.whl", hash = "sha256:f1f9f2ad5291f0225a49efad77abf9e700b6fef553900623060dad6e26503b9d"}, + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] -[package.extras] -docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] -optional = ["python-socks", "wsaccel"] -test = ["websockets"] - [[package]] name = "werkzeug" -version = "2.3.6" +version = "3.0.3" description = "The comprehensive WSGI web application library." -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "Werkzeug-2.3.6-py3-none-any.whl", hash = "sha256:935539fa1413afbb9195b24880778422ed620c0fc09670945185cce4d91a8890"}, - {file = "Werkzeug-2.3.6.tar.gz", hash = "sha256:98c774df2f91b05550078891dee5f0eb0cb797a522c757a2452b9cee5b202330"}, + {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, + {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, ] [package.dependencies] @@ -2533,103 +2455,96 @@ watchdog = ["watchdog (>=2.3)"] [[package]] name = "wrapt" -version = "1.15.0" +version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -files = [ - {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, - {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, - {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, - {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, - {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, - {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, - {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, - {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, - {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, - {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, - {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, - {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, - {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, - {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, - {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, - {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, - {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, - {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, - {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, +optional = false +python-versions = ">=3.6" +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, ] [[package]] name = "wtforms" -version = "3.0.1" +version = "3.1.2" description = "Form validation and rendering for Python web development." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "WTForms-3.0.1-py3-none-any.whl", hash = "sha256:837f2f0e0ca79481b92884962b914eba4e72b7a2daaf1f939c890ed0124b834b"}, - {file = "WTForms-3.0.1.tar.gz", hash = "sha256:6b351bbb12dd58af57ffef05bc78425d08d1914e0fd68ee14143b7ade023c5bc"}, + {file = "wtforms-3.1.2-py3-none-any.whl", hash = "sha256:bf831c042829c8cdbad74c27575098d541d039b1faa74c771545ecac916f2c07"}, + {file = "wtforms-3.1.2.tar.gz", hash = "sha256:f8d76180d7239c94c6322f7990ae1216dae3659b7aa1cee94b6318bdffb474b9"}, ] [package.dependencies] -MarkupSafe = "*" +markupsafe = "*" [package.extras] email = ["email-validator"] @@ -2638,7 +2553,6 @@ email = ["email-validator"] name = "xmltodict" version = "0.13.0" description = "Makes working with XML feel like you are working with JSON" -category = "main" optional = false python-versions = ">=3.4" files = [ @@ -2648,21 +2562,20 @@ files = [ [[package]] name = "zipp" -version = "3.16.2" +version = "3.19.2" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, - {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, + {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, + {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0.0" -content-hash = "51de5ee8af009da748bd7304d5266648018eb755a9644cad295c053b7b250344" +content-hash = "3d8d2736573ee2b07bc32fb6a4ad3d84366be2e4d5f9b4fe0dfd18286bc58c57" diff --git a/pyproject.toml b/pyproject.toml index 699fcfd96..742a05c43 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "fence" -version = "9.2.0" +version = "10.0.0" description = "Gen3 AuthN/AuthZ OIDC Service" authors = ["CTDS UChicago "] license = "Apache-2.0" @@ -13,66 +13,60 @@ include = [ [tool.poetry.dependencies] python = ">=3.9,<4.0.0" alembic = "^1.7.7" - -# Temporarily override authlib with a modified, forked version -# where we've made a security patch that got applied in a much later -# version. -# -# This is temporary while we work on the upgrade to the latest version -authlib = {git = "https://github.com/uc-cdis/authlib", rev = "v0.11_CVE_patch_v1"} -# authlib = "*" # let authutils decide which version we're using - -authutils = "^6.2.2" +authlib = "*" #let authutils decide which version to use +authutils = "^6.2.3" bcrypt = "^3.1.4" -boto3 = "~1.9.91" -botocore = "^1.12.253" +boto3 = "*" +botocore = "*" cached_property = "^1.5.1" cdiserrors = "<2.0.0" cdislogging = "^1.0.0" cdispyutils = "^2.0.1" -cryptography = ">=41.0.2" -flask = ">=2.2.3" +flask = ">=3.0.0" +cryptography = ">=42.0.5" flask-cors = ">=3.0.3" flask-restful = ">=0.3.8" email_validator = "^1.1.1" gen3authz = "^1.5.1" -gen3cirrus = "^2.0.0" +gen3cirrus = ">=3.0.1" gen3config = ">=1.1.0" -gen3users = "^0.6.0" +gen3users = "^1.0.2" idna = "^2.10" # https://github.com/python-poetry/poetry/issues/3555 markdown = "^3.1.1" # this markupsafe pin is due to an error somewhere between Python 3.9.6 and 3.9.16 markupsafe = "^2.0.1" -paramiko = "^2.6.0" +paramiko = ">=2.6.0" prometheus-client = "^0.9.0" psycopg2 = "^2.8.3" PyJWT = "^2.4.0" python_dateutil = "^2.6.1" python-jose = "^2.0.2" -pyyaml = "^5.1" +pyyaml = "^6.0.1" requests = ">=2.18.0" retry = "^0.9.2" sqlalchemy = "^1.3.3" -storageclient = {git = "https://github.com/uc-cdis/storage-client", rev = "1.0.2"} userdatamodel = ">=2.4.3" -werkzeug = ">=2.2.3" +werkzeug = ">=3.0.0" cachelib = "^0.2.0" azure-storage-blob = "^12.6.0" Flask-WTF = "^1.0.0" - +boto = "*" +# NOTE: +# for testing with updated libaries as git repos: +# foobar = {git = "https://github.com/uc-cdis/some-repo", rev = "feat/test"} [tool.poetry.dev-dependencies] addict = "^2.2.1" -cdisutilstest = {git = "https://github.com/uc-cdis/cdisutils-test", rev = "1.0.0"} +cdisutilstest = {git = "https://github.com/uc-cdis/cdisutils-test", tag = "2.0.0"} codacy-coverage = "^1.3.11" coveralls = "^2.1.1" mock = "^2.0.0" moto = "^1.1.24" -pytest = "^3.2.3" +pytest = "^5.2.0" pytest-cov = "^2.5.1" -pytest-flask = ">=0.15.0" +pytest-flask = ">=1.3.0" [tool.poetry.scripts] fence-create = 'bin.fence_create:main' diff --git a/run.py b/run.py index c75ed627c..913803c78 100644 --- a/run.py +++ b/run.py @@ -23,7 +23,7 @@ if config.get("MOCK_STORAGE"): from mock import patch - from cdisutilstest.code.storage_client_mock import get_client + from tests.storageclient.storage_client_mock import get_client patcher = patch("fence.resources.storage.get_client", get_client) patcher.start() diff --git a/tests/admin/test_admin_users_endpoints.py b/tests/admin/test_admin_users_endpoints.py index 370942b14..5a6d3a746 100644 --- a/tests/admin/test_admin_users_endpoints.py +++ b/tests/admin/test_admin_users_endpoints.py @@ -110,14 +110,12 @@ def load_non_google_user_data(db_session, test_user_d): client = Client( client_id=userd_dict["client_id"], user_id=userd_dict["user_id"], - issued_at=420, - expires_at=42020, - redirect_uri="dclient.com", - grant_type="dgrant", - response_type="dresponse", - scope="dscope", + client_id_issued_at=420, + client_secret_expires_at=42020, + redirect_uris="dclient.com", + response_types="dresponse", name="dclientname", - _allowed_scopes="dallscopes", + allowed_scopes="dallscopes", ) grp = Group(id=userd_dict["group_id"]) usr_grp = UserToGroup( @@ -676,6 +674,7 @@ def assert_google_proxy_group_data_deleted(db_session): def test_delete_user_username( + app, client, admin_user, encoded_admin_jwt, diff --git a/tests/ci_commands_script.sh b/tests/ci_commands_script.sh new file mode 100755 index 000000000..5ab1d6c6c --- /dev/null +++ b/tests/ci_commands_script.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +poetry run pytest -vv --cov=fence --cov-report xml tests diff --git a/tests/conftest.py b/tests/conftest.py index 06edd3d37..f7b9d432d 100755 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,7 +28,6 @@ ) from cryptography.fernet import Fernet import bcrypt -from cdisutilstest.code.storage_client_mock import get_client import jwt from mock import patch, MagicMock, PropertyMock import pytest @@ -53,6 +52,7 @@ from tests import utils from tests.utils import TEST_RAS_USERNAME, TEST_RAS_SUB from tests.utils.oauth2.client import OAuth2TestClient +from tests.storageclient.storage_client_mock import get_client # Allow authlib to use HTTP for local testing. @@ -86,6 +86,7 @@ def mock_get_bucket_location(self, bucket, config): def claims_refresh(): new_claims = tests.utils.default_claims() new_claims["pur"] = "refresh" + new_claims["azp"] = "test-client" return new_claims @@ -474,7 +475,6 @@ def app(kid, rsa_private_key, rsa_public_key): yield fence.app - alembic_main(["--raiseerr", "downgrade", "base"]) mocker.unmock_functions() @@ -542,7 +542,9 @@ def drop_all(): connection = app.db.engine.connect() connection.begin() for table in reversed(models.Base.metadata.sorted_tables): - connection.execute(table.delete()) + # Delete table only if it exists + if app.db.engine.dialect.has_table(connection, table): + connection.execute(table.delete()) connection.close() request.addfinalizer(drop_all) @@ -1358,7 +1360,7 @@ def oauth_client_B(app, request, db_session): @pytest.fixture(scope="function") -def oauth_client_public(app, db_session, oauth_user): +def oauth_client_public(app, db_session, oauth_user, get_all_shib_idps_patcher): """ Create a public OAuth2 client. """ @@ -1428,7 +1430,7 @@ def oauth_test_client_B(client, oauth_client_B): @pytest.fixture(scope="function") -def oauth_test_client_public(client, oauth_client_public): +def oauth_test_client_public(client, oauth_client_public, get_all_shib_idps_patcher): return OAuth2TestClient(client, oauth_client_public, confidential=False) @@ -1571,7 +1573,8 @@ def cloud_manager(): def google_signed_url(): manager = MagicMock() patch( - "fence.blueprints.data.indexd.cirrus.google_cloud.utils.get_signed_url", manager + "fence.blueprints.data.indexd.gen3cirrus.google_cloud.utils.get_signed_url", + manager, ).start() # Note: example outpu/format from google's docs, will not actually work diff --git a/tests/credentials/google/test_credentials.py b/tests/credentials/google/test_credentials.py index a355ac8f8..c32716fe9 100644 --- a/tests/credentials/google/test_credentials.py +++ b/tests/credentials/google/test_credentials.py @@ -17,7 +17,7 @@ ProjectToBucket, StorageAccess, ) -from cdisutilstest.code.storage_client_mock import get_client +from tests.storageclient.storage_client_mock import get_client from fence.config import config diff --git a/tests/data/test_blank_index.py b/tests/data/test_blank_index.py index ce560b075..7586d85be 100755 --- a/tests/data/test_blank_index.py +++ b/tests/data/test_blank_index.py @@ -348,7 +348,7 @@ def test_init_multipart_upload_missing_configuration_key(app, indexd_client): uploader = MagicMock() current_app = flask.current_app expected_value = copy.deepcopy(current_app.config) - del expected_value["DATA_UPLOAD_BUCKET"] + expected_value["DATA_UPLOAD_BUCKET"] = "" with patch.object(current_app, "config", expected_value): assert current_app.config == expected_value @@ -395,7 +395,7 @@ def test_complete_multipart_upload_missing_key(app, indexd_client): uploader = MagicMock() current_app = flask.current_app expected_value = copy.deepcopy(current_app.config) - del expected_value["DATA_UPLOAD_BUCKET"] + expected_value["DATA_UPLOAD_BUCKET"] = "" with patch.object(current_app, "config", expected_value): assert current_app.config == expected_value @@ -444,7 +444,7 @@ def test_generate_aws_presigned_url_for_part_missing_key(app, indexd_client): uploader = MagicMock() current_app = flask.current_app expected_value = copy.deepcopy(current_app.config) - del expected_value["DATA_UPLOAD_BUCKET"] + expected_value["DATA_UPLOAD_BUCKET"] = "" with patch.object(current_app, "config", expected_value): assert current_app.config == expected_value @@ -492,7 +492,7 @@ def test_make_signed_url_missing_configuration_key(app, indexd_client): current_app = flask.current_app expected_value = copy.deepcopy(current_app.config) del expected_value["AZ_BLOB_CONTAINER_URL"] - del expected_value["DATA_UPLOAD_BUCKET"] + expected_value["DATA_UPLOAD_BUCKET"] = "" indexed_file_location = indexd_client["indexed_file_location"] with patch.object(current_app, "config", expected_value): @@ -502,10 +502,8 @@ def test_make_signed_url_missing_configuration_key(app, indexd_client): with patch( "fence.blueprints.data.indexd.AzureBlobStorageIndexedFileLocation.get_signed_url" ): - with patch( - "fence.blueprints.data.indexd.S3IndexedFileLocation.get_signed_url" - ): - with pytest.raises(InternalError): - signed_url = blank_index.make_signed_url( - file_name="some file name", protocol=indexed_file_location - ) + + with pytest.raises(InternalError): + signed_url = blank_index.make_signed_url( + file_name="some file name", protocol=indexed_file_location + ) diff --git a/tests/data/test_data.py b/tests/data/test_data.py index a3f1dbbc4..6d6186bec 100755 --- a/tests/data/test_data.py +++ b/tests/data/test_data.py @@ -184,7 +184,7 @@ def test_indexd_upload_file_key_error( current_app = fence.blueprints.data.indexd.flask.current_app expected_value = copy.deepcopy(current_app.config) - del expected_value["DATA_UPLOAD_BUCKET"] + expected_value["DATA_UPLOAD_BUCKET"] = "" del expected_value["AZ_BLOB_CONTAINER_URL"] with patch.object(current_app, "config", expected_value): @@ -276,7 +276,7 @@ def test_indexd_upload_file_filename_key_error( current_app = fence.blueprints.data.indexd.flask.current_app expected_value = copy.deepcopy(current_app.config) - del expected_value["DATA_UPLOAD_BUCKET"] + expected_value["DATA_UPLOAD_BUCKET"] = "" del expected_value["AZ_BLOB_CONTAINER_URL"] with patch.object(current_app, "config", expected_value): diff --git a/tests/data/test_indexed_file.py b/tests/data/test_indexed_file.py index 5988f75fd..b310c8204 100755 --- a/tests/data/test_indexed_file.py +++ b/tests/data/test_indexed_file.py @@ -5,7 +5,7 @@ from unittest import mock from mock import patch -import cirrus +import gen3cirrus import pytest import fence.blueprints.data.indexd as indexd @@ -441,7 +441,7 @@ def test_internal_get_gs_signed_url_cache_new_key_if_old_key_expired( return_value=(sa_private_key), ): with mock.patch.object( - cirrus.google_cloud.utils, + gen3cirrus.google_cloud.utils, "get_signed_url", return_value="https://cloud.google.com/compute/url", ): @@ -514,7 +514,7 @@ def test_internal_get_gs_signed_url_clear_cache_and_parse_json( return_value=(sa_private_key), ): with mock.patch.object( - cirrus.google_cloud.utils, + gen3cirrus.google_cloud.utils, "get_signed_url", return_value="https://cloud.google.com/compute/url", ): @@ -670,7 +670,7 @@ def delete_blob(self): side_effect=Exception("url not available"), ): with patch( - "cirrus.GoogleCloudManager.delete_data_file", + "gen3cirrus.GoogleCloudManager.delete_data_file", side_effect=Exception("url not available"), ): with patch( @@ -737,7 +737,8 @@ def delete_blob(self): return_value=("", 204), ): with patch( - "cirrus.GoogleCloudManager.delete_data_file", return_value=("", 204) + "gen3cirrus.GoogleCloudManager.delete_data_file", + return_value=("", 204), ): with patch( "fence.blueprints.data.indexd.BlobServiceClient.from_connection_string", @@ -787,7 +788,7 @@ def from_connection_string(cls, container_name, blob_name): side_effect=ValueError("Invalid connection string"), ): with patch( - "cirrus.GoogleCloudManager.delete_data_file", + "gen3cirrus.GoogleCloudManager.delete_data_file", side_effect=ValueError("Invalid connection string"), ): with patch( diff --git a/tests/dbgap_sync/conftest.py b/tests/dbgap_sync/conftest.py index 48907ba5b..428fd2639 100644 --- a/tests/dbgap_sync/conftest.py +++ b/tests/dbgap_sync/conftest.py @@ -7,8 +7,11 @@ from yaml import safe_load as yaml_load from cdislogging import get_logger -from cirrus import GoogleCloudManager -from cdisutilstest.code.storage_client_mock import get_client, StorageClientMocker +from gen3cirrus import GoogleCloudManager +from tests.storageclient.storage_client_mock import ( + get_client, + StorageClientMocker, +) import pytest from userdatamodel import Base from userdatamodel.models import * diff --git a/tests/dbgap_sync/test_user_sync.py b/tests/dbgap_sync/test_user_sync.py index 579f1b416..49c69e88a 100644 --- a/tests/dbgap_sync/test_user_sync.py +++ b/tests/dbgap_sync/test_user_sync.py @@ -11,6 +11,7 @@ from fence.resources.google.access_utils import GoogleUpdateException from fence.config import config from fence.job.visa_update_cronjob import Visa_Token_Update +from fence.utils import DEFAULT_BACKOFF_SETTINGS from tests.dbgap_sync.conftest import ( get_test_encoded_decoded_visa_and_exp, @@ -497,6 +498,21 @@ def test_sync_with_google_errors(syncer, monkeypatch): syncer._update_arborist.assert_called() syncer._update_authz_in_arborist.assert_called() +@patch("fence.sync.sync_users.paramiko.SSHClient") +@patch("os.makedirs") +@patch("os.path.exists", return_value=False) +@pytest.mark.parametrize("syncer", ["google", "cleversafe"], indirect=True) +def test_sync_with_sftp_connection_errors(mock_path, mock_makedir, mock_ssh_client, syncer, monkeypatch): + """ + Verifies that when there is an sftp connection error connection, that the connection is retried the max amount of + tries as configured by DEFAULT_BACKOFF_SETTINGS + """ + monkeypatch.setattr(syncer, "is_sync_from_dbgap_server", True) + mock_ssh_client.return_value.__enter__.return_value.connect.side_effect = Exception("Authentication timed out") + # usersync System Exits if any exception is raised during download. + with pytest.raises(SystemExit): + syncer.sync() + assert mock_ssh_client.return_value.__enter__.return_value.connect.call_count == DEFAULT_BACKOFF_SETTINGS['max_tries'] @pytest.mark.parametrize("syncer", ["google", "cleversafe"], indirect=True) def test_sync_from_files(syncer, db_session, storage_client): @@ -1033,6 +1049,20 @@ def test_user_sync_with_visa_sync_job( ) +@pytest.mark.parametrize("syncer", ["cleversafe", "google"], indirect=True) +def test_revoke_all_policies_no_user(db_session, syncer): + """ + Test that function returns even when there's no user + """ + # no arborist user with that username + user_that_doesnt_exist = "foobar" + syncer.arborist_client.get_user.return_value = None + + syncer._revoke_all_policies_preserve_mfa(user_that_doesnt_exist, "mock_idp") + + # we only care that this doesn't error + assert True + @pytest.mark.parametrize("syncer", ["cleversafe", "google"], indirect=True) def test_revoke_all_policies_preserve_mfa(monkeypatch, db_session, syncer): """ @@ -1051,7 +1081,7 @@ def test_revoke_all_policies_preserve_mfa(monkeypatch, db_session, syncer): username="mockuser", identity_provider=IdentityProvider(name="mock_idp") ) syncer.arborist_client.get_user.return_value = {"policies": ["mfa_policy"]} - syncer._revoke_all_policies_preserve_mfa(user) + syncer._revoke_all_policies_preserve_mfa(user.username, user.identity_provider.name) syncer.arborist_client.revoke_all_policies_for_user.assert_called_with( user.username ) @@ -1080,7 +1110,7 @@ def test_revoke_all_policies_preserve_mfa_no_mfa(monkeypatch, db_session, syncer syncer.arborist_client.list_resources_for_user.return_value = [ "/programs/phs0001111" ] - syncer._revoke_all_policies_preserve_mfa(user) + syncer._revoke_all_policies_preserve_mfa(user.username, user.identity_provider.name) syncer.arborist_client.revoke_all_policies_for_user.assert_called_with( user.username ) @@ -1102,7 +1132,7 @@ def test_revoke_all_policies_preserve_mfa_no_idp(monkeypatch, db_session, syncer }, ) user = User(username="mockuser") - syncer._revoke_all_policies_preserve_mfa(user) + syncer._revoke_all_policies_preserve_mfa(user.username) syncer.arborist_client.revoke_all_policies_for_user.assert_called_with( user.username ) @@ -1132,7 +1162,7 @@ def test_revoke_all_policies_preserve_mfa_ensure_revoke_on_error( syncer.arborist_client.list_resources_for_user.side_effect = Exception( "Unknown error" ) - syncer._revoke_all_policies_preserve_mfa(user) + syncer._revoke_all_policies_preserve_mfa(user.username, user.identity_provider.name) syncer.arborist_client.revoke_all_policies_for_user.assert_called_with( user.username ) diff --git a/tests/google/test_access_utils.py b/tests/google/test_access_utils.py index e7d1f9269..0c8b6f4f7 100644 --- a/tests/google/test_access_utils.py +++ b/tests/google/test_access_utils.py @@ -4,8 +4,8 @@ from unittest.mock import MagicMock, patch from sqlalchemy import or_ -from cirrus.errors import CirrusError -from cirrus.google_cloud.iam import GooglePolicyMember +from gen3cirrus.errors import CirrusError +from gen3cirrus.google_cloud.iam import GooglePolicyMember import fence from fence.errors import NotFound diff --git a/tests/link/test_link.py b/tests/link/test_link.py index 57e8bafbc..ae0e61fae 100644 --- a/tests/link/test_link.py +++ b/tests/link/test_link.py @@ -177,7 +177,6 @@ def test_google_link_auth_return( # manually set cookie for initial session client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, @@ -258,7 +257,6 @@ def test_patch_google_link( # manually set cookie for initial session client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, @@ -362,7 +360,6 @@ def test_patch_google_link_account_not_in_token( # manually set cookie for initial session client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, @@ -418,7 +415,6 @@ def test_patch_google_link_account_doesnt_exist( # manually set cookie for initial session client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, @@ -487,9 +483,9 @@ def test_google_link_g_account_exists( # manually set cookie for initial session client.set_cookie( - "localhost", - config["SESSION_COOKIE_NAME"], - test_session_jwt, + key=config["SESSION_COOKIE_NAME"], + value=test_session_jwt, + domain="localhost", httponly=True, samesite="Lax", ) @@ -566,7 +562,6 @@ def test_google_link_g_account_access_extension( # manually set cookie for initial session client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, @@ -653,7 +648,6 @@ def test_google_link_g_account_exists_linked_to_different_user( # manually set cookie for initial session client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, @@ -721,7 +715,6 @@ def test_google_link_no_proxy_group( # manually set cookie for initial session client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, @@ -807,7 +800,6 @@ def test_google_link_when_google_mocked( # manually set cookie for initial session client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, diff --git a/tests/link/test_link_id_token.py b/tests/link/test_link_id_token.py index a1c54c96a..5f3dff90a 100644 --- a/tests/link/test_link_id_token.py +++ b/tests/link/test_link_id_token.py @@ -12,6 +12,7 @@ def test_google_id_token_not_linked(oauth_test_client): Test google email and link expiration are in id_token for a linked account """ data = {"confirm": "yes"} + oauth_test_client.authorize(data=data) tokens = oauth_test_client.token() id_token = jwt.decode( diff --git a/tests/login/test_fence_login.py b/tests/login/test_fence_login.py index 96495fe47..d3d60b314 100644 --- a/tests/login/test_fence_login.py +++ b/tests/login/test_fence_login.py @@ -44,6 +44,7 @@ def config_idp_in_client( ], "OPENID_CONNECT": { "fence": { + "name": "other_fence_client", "client_id": "other_fence_client_id", "client_secret": "other_fence_client_secret", "api_base_url": "http://other-fence", @@ -52,7 +53,10 @@ def config_idp_in_client( }, } ) - app.fence_client = OAuthClient(**config["OPENID_CONNECT"]["fence"]) + client = OAuthClient(app) + client.register(**config["OPENID_CONNECT"]["fence"]) + app.fence_client = client + app.config["OPENID_CONNECT"]["fence"] = config["OPENID_CONNECT"]["fence"] yield Dict( client_id=config["OPENID_CONNECT"]["fence"]["client_id"], diff --git a/tests/login/test_google_login.py b/tests/login/test_google_login.py index 17d4bb8bd..f7550b47d 100644 --- a/tests/login/test_google_login.py +++ b/tests/login/test_google_login.py @@ -27,7 +27,6 @@ def test_google_login_http_headers_are_less_than_4k_for_user_with_many_projects( }, ) client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, diff --git a/tests/login/test_login_redirect.py b/tests/login/test_login_redirect.py index 8e7742db5..6915fe1b2 100644 --- a/tests/login/test_login_redirect.py +++ b/tests/login/test_login_redirect.py @@ -91,10 +91,10 @@ def test_valid_redirect_base(app, client, idp, get_value_from_discovery_doc_patc """ if idp == "fence": mocked_generate_authorize_redirect = MagicMock( - return_value=("authorization_url", "state") + return_value={"url": "authorization_url", "state": "state"} ) mock = patch( - f"flask.current_app.fence_client.generate_authorize_redirect", + f"authlib.integrations.flask_client.apps.FlaskOAuth2App.create_authorization_url", mocked_generate_authorize_redirect, ).start() diff --git a/tests/migrations/README.md b/tests/migrations/README.md new file mode 100644 index 000000000..e2c3eb999 --- /dev/null +++ b/tests/migrations/README.md @@ -0,0 +1,7 @@ +## Migration Tests + +These tests are designed to test pre/post behavior of database migrations and making sure the changes are working as intended. + +Currently we only have upgrade tests because the latest version of authlib has undergone major changes and will *not* work with previous versions of database schema. + +For client class details, see fence/models.py diff --git a/tests/migrations/test_9b3a5a7145d7.py b/tests/migrations/test_9b3a5a7145d7.py new file mode 100644 index 000000000..3dca1b50d --- /dev/null +++ b/tests/migrations/test_9b3a5a7145d7.py @@ -0,0 +1,61 @@ +""" +"Non-unique client name" migration +""" + +from alembic.config import main as alembic_main +import pytest +from sqlalchemy.exc import IntegrityError + +from fence.models import Client +from fence.utils import random_str +import bcrypt + + +@pytest.fixture(scope="function", autouse=True) +def post_test_clean_up(app): + yield + + # clean up the client table + with app.db.session as db_session: + db_session.query(Client).delete() + + # go back to the latest state of the DB + alembic_main(["--raiseerr", "upgrade", "head"]) + + +def test_upgrade(app): + """Test Adding Client after performing Alembic Upgrade to this revision""" + alembic_main(["--raiseerr", "upgrade", "9b3a5a7145d7"]) # pragma: allowlist secret + + client_name = "client_name" + url = "https://oauth-client.net" + client_id = "test-client" + client_secret = random_str(50) + hashed_secret = bcrypt.hashpw( + client_secret.encode("utf-8"), bcrypt.gensalt() + ).decode("utf-8") + grant_types = ["refresh_token"] + allowed_scopes = ["openid", "user", "fence"] + with app.db.session as db_session: + db_session.add( + Client( + client_id=client_id, + client_secret=hashed_secret, + allowed_scopes=allowed_scopes, + redirect_uris=[url], + description="", + is_confidential=True, + name=client_name, + grant_types=grant_types, + ) + ) + db_session.commit() + query_result = db_session.query(Client).all() + + # make sure the client was created and the new _client_metadata field is populated and Authlib getters are working + assert len(query_result) == 1, query_result + assert query_result[0].name == client_name + assert query_result[0].client_secret == hashed_secret + assert query_result[0].scope == " ".join(allowed_scopes) + assert query_result[0].grant_types == grant_types + assert query_result[0].redirect_uris == [url] diff --git a/tests/migrations/test_a04a70296688.py b/tests/migrations/test_a04a70296688.py index ab3560420..bf1e8b6dc 100644 --- a/tests/migrations/test_a04a70296688.py +++ b/tests/migrations/test_a04a70296688.py @@ -23,36 +23,12 @@ def post_test_clean_up(app): def test_upgrade(app): - # state before migration - alembic_main(["--raiseerr", "downgrade", "ea7e1b843f82"]) + # This is the last version our current codebase will work with + alembic_main(["--raiseerr", "upgrade", "9b3a5a7145d7"]) # pragma: allowlist secret client_name = "non_unique_client_name" - # before the migration, it should not be possible to create 2 clients - # with the same name - with app.db.session as db_session: - db_session.add( - Client( - name=client_name, - client_id="client_id1", - grant_types="client_credentials", - ) - ) - db_session.add( - Client( - name=client_name, - client_id="client_id2", - grant_types="client_credentials", - ) - ) - with pytest.raises(IntegrityError): - db_session.commit() - db_session.rollback() - - # run the upgrade migration - alembic_main(["--raiseerr", "upgrade", "a04a70296688"]) - - # now it should be possible + # It should be possible to add 2 clients of the same name with app.db.session as db_session: db_session.add( Client( @@ -75,74 +51,3 @@ def test_upgrade(app): assert len(query_result) == 2, query_result assert query_result[0].name == client_name assert query_result[1].name == client_name - - -@pytest.mark.parametrize("expirations", [[1, 100], [0, 0], [0, 100]]) -def test_downgrade(app, expirations): - """ - Test the downgrade with the following expiration values: - - 1 and 100: we keep the row with the highest expiration (100) - - 0 and 0: both rows have no expiration: we keep any of the 2 - - 0 and 100: we keep the row that has an expiration (100) - """ - # state after migration - alembic_main(["--raiseerr", "downgrade", "a04a70296688"]) - - client_name = "non_unique_client_name" - - # it should be possible to create 2 clients with the same name - with app.db.session as db_session: - db_session.add( - Client( - name=client_name, - client_id="client_id1", - grant_types="client_credentials", - expires_in=expirations[0], - ) - ) - db_session.add( - Client( - name=client_name, - client_id="client_id2", - grant_types="client_credentials", - expires_in=expirations[1], - ) - ) - db_session.commit() - query_result = db_session.query(Client).all() - - assert len(query_result) == 2, query_result - assert query_result[0].name == client_name - expires_at1 = query_result[0].expires_at - assert query_result[1].name == client_name - expires_at2 = query_result[1].expires_at - - # run the downgrade migration - alembic_main(["--raiseerr", "downgrade", "ea7e1b843f82"]) - - # the duplicate row with the lowest expiration should have been deleted - with app.db.session as db_session: - query_result = db_session.query(Client).all() - assert len(query_result) == 1, query_result - assert query_result[0].name == client_name - assert query_result[0].expires_at == max(expires_at1, expires_at2) - - # now it should not be possible anymore to create 2 clients with the same name - with app.db.session as db_session: - db_session.add( - Client( - name=client_name, - client_id="client_id1", - grant_types="client_credentials", - ) - ) - db_session.add( - Client( - name=client_name, - client_id="client_id2", - grant_types="client_credentials", - ) - ) - with pytest.raises(IntegrityError): - db_session.commit() - db_session.rollback() diff --git a/tests/migrations/test_ea7e1b843f82.py b/tests/migrations/test_ea7e1b843f82.py deleted file mode 100644 index 57c9e129d..000000000 --- a/tests/migrations/test_ea7e1b843f82.py +++ /dev/null @@ -1,122 +0,0 @@ -""" -"Optional Client.redirect_uri" migration -""" - -from alembic.config import main as alembic_main -import pytest -from sqlalchemy.exc import IntegrityError - -from fence.models import Client - - -@pytest.fixture(scope="function", autouse=True) -def post_test_clean_up(app): - yield - - # clean up the client table - with app.db.session as db_session: - db_session.query(Client).delete() - - # go back to the latest state of the DB - alembic_main(["--raiseerr", "upgrade", "head"]) - - -def test_upgrade(app): - # state before migration - alembic_main(["--raiseerr", "downgrade", "e4c7b0ab68d3"]) - - # before the migration, it should not be possible to create a client - # without a redirect_uri - with app.db.session as db_session: - with pytest.raises(IntegrityError): - db_session.add( - Client( - client_id="client_without_redirect_uri", - name="client_without_redirect_uri_name", - grant_types="client_credentials", - ) - ) - db_session.commit() - db_session.rollback() - - # run the upgrade migration - alembic_main(["--raiseerr", "upgrade", "ea7e1b843f82"]) - - # now it should be possible - with app.db.session as db_session: - db_session.add( - Client( - client_id="client_without_redirect_uri", - name="client_without_redirect_uri_name", - grant_types="client_credentials", - ) - ) - db_session.commit() - query_result = db_session.query(Client).all() - - # make sure the client was created - assert len(query_result) == 1, query_result - assert query_result[0].client_id == "client_without_redirect_uri" - assert query_result[0].redirect_uri == None - - -def test_downgrade(app): - # state after migration - alembic_main(["--raiseerr", "downgrade", "ea7e1b843f82"]) - - with app.db.session as db_session: - # it should possible to create a client without a redirect_uri - db_session.add( - Client( - client_id="client_without_redirect_uri", - name="client_without_redirect_uri_name", - grant_types="client_credentials", - ) - ) - # also create a client with a redirect_uri - db_session.add( - Client( - client_id="client_with_redirect_uri", - name="client_with_redirect_uri_name", - grant_types="client_credentials", - redirect_uri="http://localhost/redirect", - ) - ) - query_result = db_session.query(Client).all() - - # make sure the clients were created - assert len(query_result) == 2, query_result - - client_without_redirect_uri = [ - c for c in query_result if c.client_id == "client_without_redirect_uri" - ] - assert len(client_without_redirect_uri) == 1 - assert client_without_redirect_uri[0].redirect_uri == None - - client_with_redirect_uri = [ - c for c in query_result if c.client_id == "client_with_redirect_uri" - ] - assert len(client_with_redirect_uri) == 1 - assert client_with_redirect_uri[0].redirect_uri == "http://localhost/redirect" - - # run the downgrade migration - alembic_main(["--raiseerr", "downgrade", "e4c7b0ab68d3"]) - - with app.db.session as db_session: - query_result = db_session.query(Client).all() - assert len(query_result) == 2, query_result - - # make sure the client without redirect was migrated to have an empty - # string as redirect_uri instead of null - client_without_redirect_uri = [ - c for c in query_result if c.client_id == "client_without_redirect_uri" - ] - assert len(client_without_redirect_uri) == 1 - assert client_without_redirect_uri[0].redirect_uri == "" - - # make sure the client with redirect is unchanged - client_with_redirect_uri = [ - c for c in query_result if c.client_id == "client_with_redirect_uri" - ] - assert len(client_with_redirect_uri) == 1 - assert client_with_redirect_uri[0].redirect_uri == "http://localhost/redirect" diff --git a/tests/oidc/core/token/test_validation.py b/tests/oidc/core/token/test_validation.py index b24f96990..d600406a5 100644 --- a/tests/oidc/core/token/test_validation.py +++ b/tests/oidc/core/token/test_validation.py @@ -35,7 +35,7 @@ def test_reuse_code_invalid(oauth_test_client): """ Test that an authorization code returned from the authorization endpoint can be used only once, and after that its attempted usage will return an - ``invalid_request`` error. + ``invalid_grant`` error. """ code = oauth_test_client.authorize(data={"confirm": "yes"}).code # Test that the first time using the code is fine. @@ -45,20 +45,20 @@ def test_reuse_code_invalid(oauth_test_client): response = oauth_test_client.token_response.response assert response.status_code == 400 assert "error" in response.json - assert response.json["error"] == "invalid_request" + assert response.json["error"] == "invalid_grant" def test_different_client_invalid(oauth_test_client, oauth_test_client_B): """ Test that one client cannot use an authorization code which was issued to a - different client, and the request fails with ``invalid_request``. + different client, and the request fails with ``invalid_grant``. """ code = oauth_test_client.authorize(data={"confirm": "yes"}).code # Have client B send the code to the token endpoint. response = oauth_test_client_B.token(code=code, do_asserts=False).response assert response.status_code == 400 assert "error" in response.json - assert response.json["error"] == "invalid_request" + assert response.json["error"] == "invalid_grant" def test_invalid_code(oauth_test_client): @@ -69,27 +69,27 @@ def test_invalid_code(oauth_test_client): response = oauth_test_client.token(code=code, do_asserts=False).response assert response.status_code == 400 assert "error" in response.json - assert response.json["error"] == "invalid_request" + assert response.json["error"] == "invalid_grant" def test_invalid_redirect_uri(oauth_test_client): """ Test that if the token request has a different redirect_uri than the one the client is suppsed to be using that an error is raised, with the - ``invalid_request`` code. + ``invalid_grant`` code. """ oauth_test_client.authorize(data={"confirm": "yes"}) data = {"redirect_uri": oauth_test_client.url + "/some-garbage"} response = oauth_test_client.token(data=data, do_asserts=False).response assert response.status_code == 400 assert "error" in response.json - assert response.json["error"] == "invalid_request" + assert response.json["error"] == "invalid_grant" def test_no_redirect_uri(client, oauth_test_client): """ Test that if the token request has no ``redirect_uri`` that an error is - raised, with the ``invalid_request`` code. + raised, with the ``invalid_grant`` code. """ code = oauth_test_client.authorize(data={"confirm": "yes"}).code headers = oauth_test_client._auth_header @@ -105,4 +105,4 @@ def test_no_redirect_uri(client, oauth_test_client): ) assert token_response.status_code == 400 assert "error" in token_response.json - assert token_response.json["error"] == "invalid_request" + assert token_response.json["error"] == "invalid_grant" diff --git a/tests/rfc6749/test_oauth2.py b/tests/rfc6749/test_oauth2.py index 11487d3e6..89879b550 100644 --- a/tests/rfc6749/test_oauth2.py +++ b/tests/rfc6749/test_oauth2.py @@ -28,7 +28,8 @@ def test_oauth2_authorize_incorrect_scope(oauth_test_client, method): auth_response = oauth_test_client.authorize( method=method, data=data, do_asserts=False ) - assert auth_response.response.status_code == 401 + # Check the status code is not a redirect code 3xx + assert str(auth_response.response.status_code)[0] != "3" @pytest.mark.parametrize("method", ["GET", "POST"]) diff --git a/tests/scripting/test_fence-create.py b/tests/scripting/test_fence-create.py index 29090837f..deae90d34 100644 --- a/tests/scripting/test_fence-create.py +++ b/tests/scripting/test_fence-create.py @@ -5,8 +5,8 @@ from unittest.mock import MagicMock, patch import pytest -import cirrus -from cirrus.google_cloud.errors import GoogleAuthError +import gen3cirrus +from gen3cirrus.google_cloud.errors import GoogleAuthError from userdatamodel.models import Group from fence.config import config @@ -113,7 +113,7 @@ def test_create_client_inits_default_allowed_scopes(db_session): def to_test(): saved_client = db_session.query(Client).filter_by(name=client_name).first() - assert saved_client._allowed_scopes == " ".join(config["CLIENT_ALLOWED_SCOPES"]) + assert saved_client.scope == " ".join(config["CLIENT_ALLOWED_SCOPES"]) create_client_action_wrapper( to_test, @@ -131,7 +131,7 @@ def test_create_client_inits_passed_allowed_scopes(db_session): def to_test(): saved_client = db_session.query(Client).filter_by(name=client_name).first() - assert saved_client._allowed_scopes == "openid user data" + assert saved_client.scope == "openid user data" create_client_action_wrapper( to_test, @@ -150,7 +150,7 @@ def test_create_client_adds_openid_when_not_in_allowed_scopes(db_session): def to_test(): saved_client = db_session.query(Client).filter_by(name=client_name).first() - assert saved_client._allowed_scopes == "user data openid" + assert saved_client.scope == "user data openid" create_client_action_wrapper( to_test, @@ -490,7 +490,7 @@ def test_client_rotate(db_session): for attr in [ "user", "redirect_uris", - "_allowed_scopes", + "scope", "description", "auto_approve", "grant_types", @@ -537,21 +537,23 @@ def test_client_rotate_and_actions(db_session, capsys): capsys.readouterr() # clear the buffer list_client_action(db_session) captured_logs = str(capsys.readouterr()) - assert captured_logs.count("'name': 'client_abc'") == 3 + assert captured_logs.count("'name\\': \\'client_abc\\'") == 3 for i in range(3): - assert captured_logs.count(f"'client_id': '{clients[i].client_id}'") == 1 + assert ( + captured_logs.count(f"\\'client_id\\': \\'{clients[i].client_id}\\'") == 1 + ) # check that `modify_client_action` updates all the rows description = "new description" url2 = "new url" modify_client_action( - db_session, client_name, description=description, urls=[url2], append=True + config["DB"], client_name, description=description, urls=[url2], append=True ) clients = db_session.query(Client).filter_by(name=client_name).all() assert len(clients) == 3 for i in range(3): assert clients[i].description == description - assert clients[i].redirect_uri == f"{url1}\n{url2}" + assert clients[i].redirect_uris == [url1, url2] # check that `delete_client_action` deletes all the rows delete_client_action(config["DB"], client_name) @@ -670,13 +672,12 @@ def test_create_user_access_token( def test_create_refresh_token_with_found_user( app, db_session, oauth_test_client, kid, rsa_private_key ): - DB = config["DB"] username = "test_user" BASE_URL = config["BASE_URL"] scopes = "openid,user" expires_in = 3600 - + client_id = "test-client" user = User(username=username) db_session.add(user) @@ -690,6 +691,7 @@ def test_create_refresh_token_with_found_user( scopes=scopes, expires_in=expires_in, private_key=rsa_private_key, + client_id=client_id, ).create_refresh_token() refresh_token_response = oauth_test_client.refresh( @@ -818,7 +820,7 @@ def test_delete_expired_service_accounts_with_one_fail_first( import fence fence.settings = MagicMock() - cirrus.config.update = MagicMock() + gen3cirrus.config.update = MagicMock() cloud_manager.return_value.__enter__.return_value.remove_member_from_group.side_effect = [ HttpError(mock.Mock(status=403), bytes("Permission denied", "utf-8")), {}, @@ -1129,7 +1131,7 @@ def test_delete_expired_google_access_with_one_fail_first( import fence fence.settings = MagicMock() - cirrus.config.update = MagicMock() + gen3cirrus.config.update = MagicMock() cloud_manager.return_value.__enter__.return_value.remove_member_from_group.side_effect = [ HttpError(mock.Mock(status=403), bytes("Permission denied", "utf-8")), {}, @@ -1391,7 +1393,6 @@ def test_verify_google_service_account_member_not_call_delete_operation( def test_link_external_bucket(app, cloud_manager, db_session): - (cloud_manager.return_value.__enter__.return_value.create_group.return_value) = { "email": "test_bucket_read_gbag@someemail.com" } @@ -1719,7 +1720,7 @@ def test_modify_client_action_modify_allowed_scopes(db_session): client_id=client_id, client_secret="secret", # pragma: allowlist secret name=client_name, - _allowed_scopes="openid user data", + allowed_scopes="openid user data", user=User(username="client_user"), redirect_uris=["localhost"], ) @@ -1738,7 +1739,7 @@ def test_modify_client_action_modify_allowed_scopes(db_session): assert client.auto_approve == True assert client.name == "test321" assert client.description == "test client" - assert client._allowed_scopes == "openid user test" + assert client.scope == "openid user test" assert client.redirect_uris == ["test"] @@ -1749,7 +1750,7 @@ def test_modify_client_action_modify_allowed_scopes_append_true(db_session): client_id=client_id, client_secret="secret", # pragma: allowlist secret name=client_name, - _allowed_scopes="openid user data", + allowed_scopes="openid user data", user=User(username="client_user"), redirect_uris=["localhost"], ) @@ -1768,9 +1769,7 @@ def test_modify_client_action_modify_allowed_scopes_append_true(db_session): assert client.auto_approve == True assert client.name == "test321" assert client.description == "test client" - assert ( - client._allowed_scopes == "openid user data new_scope new_scope_2 new_scope_3" - ) + assert client.scope == "openid user data new_scope new_scope_2 new_scope_3" def test_modify_client_action_modify_append_url(db_session): @@ -1780,7 +1779,7 @@ def test_modify_client_action_modify_append_url(db_session): client_id=client_id, client_secret="secret", # pragma: allowlist secret name=client_name, - _allowed_scopes="openid user data", + allowed_scopes="openid user data", user=User(username="client_user"), redirect_uris="abcd", ) diff --git a/tests/session/test_session.py b/tests/session/test_session.py index 387dd7e98..cf8c812b0 100644 --- a/tests/session/test_session.py +++ b/tests/session/test_session.py @@ -24,8 +24,7 @@ def test_session_cookie_creation(app): with app.test_client() as client: with client.session_transaction(): pass - - client_cookies = [cookie.key for cookie in client.cookie_jar] + client_cookies = client.get_cookie(config["SESSION_COOKIE_NAME"]) assert not client_cookies @@ -36,15 +35,9 @@ def test_session_cookie_creation_session_modified(app): with client.session_transaction() as session: session["username"] = "Captain Janeway" - client_cookies = [cookie.key for cookie in client.cookie_jar] - assert config["SESSION_COOKIE_NAME"] in client_cookies - session_cookie = [ - cookie - for cookie in client.cookie_jar - if cookie.key == config["SESSION_COOKIE_NAME"] - ] - assert len(session_cookie) == 1 - assert session_cookie[0].value # Make sure it's not empty + session_cookie = client.get_cookie(config["SESSION_COOKIE_NAME"]) + assert session_cookie + assert session_cookie.value # Make sure it's not empty def test_valid_session(app): @@ -58,8 +51,8 @@ def test_valid_session(app): # the username with app.test_client() as client: # manually set cookie for initial session + # domain is set to localhost be default client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, @@ -82,7 +75,6 @@ def test_valid_session_modified(app): with app.test_client() as client: # manually set cookie for initial session client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, @@ -112,7 +104,6 @@ def test_expired_session_lifetime(app): with app.test_client() as client: # manually set cookie for initial session client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, @@ -144,7 +135,6 @@ def test_expired_session_timeout(app): with app.test_client() as client: # manually set cookie for initial session client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, @@ -168,9 +158,8 @@ def test_session_cleared(app): with app.test_client() as client: # manually set cookie for initial session client.set_cookie( - "localhost", - config["SESSION_COOKIE_NAME"], - test_session_jwt, + key=config["SESSION_COOKIE_NAME"], + value=test_session_jwt, httponly=True, samesite="Lax", ) @@ -178,8 +167,8 @@ def test_session_cleared(app): session["username"] = username session.clear() assert session.get("username") != username - client_cookies = [cookie.key for cookie in client.cookie_jar] - assert config["SESSION_COOKIE_NAME"] not in client_cookies + client_cookie = client.get_cookie(config["SESSION_COOKIE_NAME"]) + assert not client_cookie def test_invalid_session_cookie(app): @@ -190,7 +179,6 @@ def test_invalid_session_cookie(app): with app.test_client() as client: # manually set cookie for initial session client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, @@ -234,14 +222,12 @@ def test_valid_session_valid_access_token( with app.test_client() as client: # manually set cookie for initial session client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, samesite="Lax", ) client.set_cookie( - "localhost", config["ACCESS_TOKEN_COOKIE_NAME"], test_access_jwt, httponly=True, @@ -287,14 +273,12 @@ def test_valid_session_valid_access_token_diff_user( with app.test_client() as client: # manually set cookie for initial session client.set_cookie( - "localhost", config["SESSION_COOKIE_NAME"], test_session_jwt, httponly=True, samesite="Lax", ) client.set_cookie( - "localhost", config["ACCESS_TOKEN_COOKIE_NAME"], test_access_jwt, httponly=True, diff --git a/tests/storageclient/__init__.py b/tests/storageclient/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/storageclient/conftest.py b/tests/storageclient/conftest.py new file mode 100644 index 000000000..26c5cb76d --- /dev/null +++ b/tests/storageclient/conftest.py @@ -0,0 +1,98 @@ +import pytest + +# Python 2 and 3 compatible +try: + from unittest.mock import MagicMock + from unittest.mock import patch +except ImportError: + from mock import MagicMock + from mock import patch + +from cdisutilstest.code.request_mocker import RequestMocker +from cdisutilstest.data import ( + createAccount, + cred, + deleteAccount, + editAccountAccessKey, + editAccount, + editVault, + editVaultTemplate, + listAccounts, + listVaults, + viewSystem, +) +from fence.resources.storage.storageclient.google import GoogleCloudStorageClient + + +@pytest.fixture +def google_cloud_storage_client(): + cred.credentials.update({"google_project_id": "test-google-project"}) + return GoogleCloudStorageClient(cred.credentials) + + +@pytest.fixture(scope="function") +def test_cloud_manager(): + manager = MagicMock() + + def mocked_get_group(username): + response = {} + if username == "user0": + response = {"email": "user0_proxy_group@example.com"} + return response + + def mocked_add_member_to_group(member_email, group_id): + response = {} + if ( + group_id == "test_bucket" + and member_email == "user0_proxy_group@example.com" + ): + response = {"email": "user0_proxy_group@example.com"} + else: + raise Exception("cannot add {} to group {}".format(member_email, group_id)) + return response + + def mocked_remove_member_from_group(member_email, group_id): + if ( + group_id == "test_bucket" + and member_email == "user0_proxy_group@example.com" + ): + return {} + else: + raise Exception( + "cannot remove {} from group {}".format(member_email, group_id) + ) + + manager.return_value.__enter__.return_value.get_group = mocked_get_group + manager.return_value.__enter__.return_value.add_member_to_group = ( + mocked_add_member_to_group + ) + manager.return_value.__enter__.return_value.remove_member_from_group = ( + mocked_remove_member_from_group + ) + + patch( + "fence.resources.storage.storageclient.google.GoogleCloudManager", manager + ).start() + return manager + + +@pytest.fixture +def request_mocker(): + files = { + "createAccount": createAccount.values, + "deleteAccount": deleteAccount.values, + "editAccountAccessKey": editAccountAccessKey.values, + "editAccount": editAccount.values, + "editVault": editVault.values, + "editVaultTemplate": editVaultTemplate.values, + "listAccounts": listAccounts.values, + "listVaults": listVaults.values, + "viewSystem": viewSystem.values, + } + req_mock = RequestMocker(files) + patcher = patch("requests.request", req_mock.fake_request) + patcher.start() + + yield req_mock + + patcher.stop() diff --git a/tests/storageclient/storage_client_mock.py b/tests/storageclient/storage_client_mock.py new file mode 100644 index 000000000..5867d13da --- /dev/null +++ b/tests/storageclient/storage_client_mock.py @@ -0,0 +1,225 @@ +""" +This module provides the necessary methods +for mocking the module +userapi.resources.storageclient.__init__.py +modules operations +""" +import unittest, random, string +from mock import patch +from fence.resources.storage.storageclient.base import User, Bucket +from fence.resources.storage.storageclient.errors import NotFoundError, RequestError + + +def get_client(config, backend): + if backend in ["cleversafe", "google"]: + return StorageClientMocker(backend) + else: + raise NotImplementedError() + + +class StorageClientMocker(object): + """ + This class will contain the methods and + the state of the mocking object. It is + supposed to be modifiable by the very calls + it is mocking + """ + + def __init__(self, provider, users={}, buckets={}, permisions={}): + """ + users = {'Name1': User1, 'Name2': User2...} + buckets = {'Name1': Bucket1, 'Name2': Bucket2...} + """ + self.users = users + self.buckets = buckets + self.provider = provider + self.user_counter = 0 + self.bucket_counter = 0 + + def provider(self): + """ + Returns whatever is set up on the attribute + """ + return self.provider + + def list_users(self): + """ + Returns the list of users that + we have created + """ + return self.users.values() + + def has_bucket_access(self, bucket, username): + """ + Check permissions on a user and a bucket + """ + try: + return bucket in self.users[username].permissions.keys() + except KeyError: + raise NotFoundError("User not found") + + def get_user(self, name): + """ + Tries to retrieve a user from the dict + """ + return self.users.get(name) + + def list_bucket(self, backend): + """ + Returns the list of users + """ + return self.buckets.values() + + def create_user(self, name): + """ + Create and return a new user + and add it to the dict + """ + if not name in self.users.keys(): + new_user = User(name) + self.users[name] = new_user + return new_user + else: + raise RequestError("User already exists", 400) + + def delete_user(self, name): + """ + Removes a user from the list + """ + if name in self.users.keys(): + del self.users[name] + else: + raise NotFoundError("User doesn't exists") + + def delete_keypair(self, name, access_key): + """ + Delete the keypair from the user + """ + try: + the_user = self.users[name] + the_user.keys = [ + key for key in the_user.keys if key["access_key"] != access_key + ] + except KeyError as e: + raise e + + def delete_all_keypairs(self, name): + """ + Deletes all keypairs from a user + """ + try: + self.users[name].keys = [] + except KeyError: + raise NotFoundError("The user doesn't exist") + + def create_keypair(self, name): + """ + Create a fake keypair for a user + """ + try: + the_user = self.users[name] + access_key = "".join( + random.choice(string.ascii_uppercase + string.digits) for _ in range(8) + ) + secret_key = "".join( + random.choice(string.ascii_uppercase + string.digits) for _ in range(16) + ) + new_key = {"access_key": access_key, "secret_key": secret_key} + the_user.keys.append(new_key) + return new_key + except KeyError as e: + raise e + + def get_bucket(self, name): + """ + Retrieve a bucket from the list + """ + try: + return self.buckets[name] + except KeyError: + raise NotFoundError("Bucket not found") + + def get_or_create_user(self, name): + """ + Try to get a user and if it fails + creates a new one + """ + return self.get_user(name) or self.create_user(name) + + def get_or_create_bucket(self, name): + """ + Tries to get a bucket and if it fails + creates a new one + """ + try: + return self.get_bucket(name) + except NotFoundError: + mock_key = "XXXXXXXXXX" + mock_secret = "YYYYYYYYYYYYYYYYYY" + return self.create_bucket(name, mock_key, mock_secret) + + def create_bucket(self, name, access_key=None, secret_key=None): + """ + Create a user and insert it in our dictionary + """ + if not name in self.buckets.keys(): + self.bucket_counter += 1 + bucket = Bucket(name, self.bucket_counter, 1024) + self.buckets[name] = bucket + else: + raise RequestError("Bucket name already exists", 400) + + def edit_bucket_template(self, template_id, **kwargs): + """ + Modifies the template + """ + if template_id == 1: + return None + else: + raise NotFoundError("Template not found") + + def update_bucket_acl(self, bucket, new_grant): + """ + Updates the bucket ACL + """ + if bucket in self.buckets.keys(): + return None + else: + raise RequestError("Bucket not found", 400) + + def set_bucket_quota(self, bucket, quota_unit, quota): + try: + self.buckets[bucket].quota = quota + except KeyError: + raise RequestError("Bucket not found", 400) + + def add_bucket_acl(self, bucket, user, access=None): + if not bucket in self.buckets.keys(): + raise NotFoundError("Bucket not found") + elif not user in self.users.keys(): + raise NotFoundError("Bucket not found") + else: + self.users[user].permissions[bucket] = access + + def delete_bucket_acl(self, bucket, user): + """ + Remove user's permission from a bucket + Args: + bucket (str): bucket name + user (str): user name + Returns: + None + """ + if not bucket in self.buckets.keys(): + raise NotFoundError("Bucket not found") + elif not user in self.users.keys(): + raise NotFoundError("Bucket not found") + else: + self.users[user].permissions[bucket] = [] + + def delete_bucket(self, bucket_name): + try: + del self.buckets[bucket_name] + return None + except: + return None diff --git a/tests/storageclient/test_cleversafe_api_client.py b/tests/storageclient/test_cleversafe_api_client.py new file mode 100644 index 000000000..2aeb11991 --- /dev/null +++ b/tests/storageclient/test_cleversafe_api_client.py @@ -0,0 +1,326 @@ +""" +Module for mocking and testing of the +cleversafe API client +""" + +import unittest +from os import path, sys + +sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) +from fence.resources.storage.storageclient.cleversafe import CleversafeClient +import json +from mock import patch +from fence.resources.storage.storageclient.errors import RequestError, NotFoundError +from cdisutilstest.code.request_mocker import RequestMocker +from cdisutilstest.data import ( + createAccount, + cred, + deleteAccount, + editAccountAccessKey, + editAccount, + editVault, + editVaultTemplate, + listAccounts, + listVaults, + viewSystem, +) + + +class CleversafeManagerTests(unittest.TestCase): + """ + The tests will use a fake response + contructed from data stored in files + on the data folder. + """ + + def setUp(self): + files = { + "createAccount": createAccount.values, + "deleteAccount": deleteAccount.values, + "editAccountAccessKey": editAccountAccessKey.values, + "editAccount": editAccount.values, + "editVault": editVault.values, + "editVaultTemplate": editVaultTemplate.values, + "listAccounts": listAccounts.values, + "listVaults": listVaults.values, + "viewSystem": viewSystem.values, + } + self.req_mock = RequestMocker(files) + self.patcher = patch("requests.request", self.req_mock.fake_request) + self.patcher.start() + self.cm = CleversafeClient(cred.credentials) + + def tearDown(self): + self.patcher.stop() + + def test_get_user_success(self): + """ + Successful retrieval of a user + """ + user = self.cm.get_user("ResponseSuccess") + self.assertEqual(user.username, "ResponseSuccess") + self.assertEqual(user.permissions, {"testVaultName": "owner"}) + self.assertEqual(user.keys[0]["access_key"], "XXXXXXXXXXXXXXXXXXXXXX") + self.assertEqual(user.keys[0]["secret_key"], "YYYYYYYYYYYYYYYYYYYYYYYYYYYYY") + self.assertEqual(user.id, 72) + + def test_get_user_inexistent_user(self): + """ + Retrieval of a nonexistent user + """ + user = self.cm.get_user("NonExistent") + self.assertEqual(user, None) + + def test_get_bucket_by_id_success(self): + """ + Successful retrieval of a vault + """ + response = self.cm._get_bucket_by_id(274) + vault = json.loads(response.text) + self.assertEqual(vault["responseData"]["vaults"][0]["id"], 274) + + def test_list_buckets_success(self): + """ + Successful retrieval of all buckets + """ + vault_list = self.cm.list_buckets() + self.assertEqual(vault_list[0].id, 1) + self.assertEqual(vault_list[1].id, 2) + self.assertEqual(vault_list[2].id, 274) + self.assertEqual(vault_list[3].id, 3) + self.assertEqual(vault_list[0].name, "Testforreal") + self.assertEqual(vault_list[1].name, "whateverName") + self.assertEqual(vault_list[2].name, "testVaultName") + self.assertEqual(vault_list[3].name, "testdata3") + + def test_list_users_success(self): + """ + Successful retrieval of all users from the database + in the form of a list of User objects + """ + user_list = self.cm.list_users() + self.assertEqual(user_list[0].id, 72) + self.assertEqual(user_list[1].id, 1) + self.assertEqual(user_list[2].id, 95) + + def test_create_user_success(self): + """ + Successful creation of a user + """ + user = self.cm.create_user("testUserToBeDeleted") + self.assertEqual(user.id, 72) + self.assertEqual(user.keys[0]["access_key"], "XXXXXXXXXXXXXXXXXXXXXX") + + def test_delete_user_success(self): + """ + Successful deletion of a user + """ + response = self.cm.delete_user("ResponseSuccess") + self.assertEqual(response, None) + + def test_create_keypair_success(self): + """ + Successful creation of a key for a specific user + """ + keypair = self.cm.create_keypair("KeyPairUser") + self.assertEqual( + keypair, + { + "access_key": "XXXXXXXXXXXXXX", + "secret_key": "AAAAAAAAAAAAAHHHHHHHHHHHHHHHHHHHNNNNNN", + }, + ) + + def test_delete_keypair_success(self): + """ + Successful deletion of a key + """ + response = self.cm.delete_keypair("KeyPairUser", "XXXXXXXXXXXXXX") + self.assertEqual(response, None) + + def test_delete_keypair_inexistent_key(self): + """ + Removal of an inexistent key + """ + with self.assertRaises(RequestError): + self.cm.delete_keypair("KeyPairUser", "YYYYYYYYYYYYYYY") + + def test_set_bucket_quota_succes(self): + """ + Successful change of a bucket quota + """ + response = self.cm.set_bucket_quota("Testforreal", "TB", "1") + self.assertEqual(response.status_code, 200) + + def test_set_bucket_quota_error_response(self): + """ + Set bucket quota with error response + """ + with self.assertRaises(RequestError): + self.cm.set_bucket_quota("whateverName", "TB", "1") + + def test_list_users_error_response(self): + """ + List users with error response + """ + self.patcher.stop() + self.patcher = patch( + "requests.request", self.req_mock.fake_request_only_failure + ) + self.patcher.start() + with self.assertRaises(RequestError): + self.cm.get_user("ResponseError") + + def test_get_user_error_response(self): + """ + Get user with error response + """ + with self.assertRaises(RequestError): + self.cm.get_user("ResponseError") + + def test_delete_keypair_error_response(self): + """ + Remove key with error response + """ + with self.assertRaises(RequestError): + self.cm.delete_keypair("KeyPairUser", "YYYYYYYYYYYYYYY") + + def test_delete_all_keypairs_success(self): + """ + Remove all keys success + """ + response = self.cm.delete_all_keypairs("KeyPairUser") + self.assertEqual(response, None) + + def test_delete_all_keypairs_response_error(self): + """ + Remove all keys with response error + """ + with self.assertRaises(RequestError): + self.cm.delete_all_keypairs("KeyPairErrorUser") + + def test_create_keypair_response_error(self): + """ + Key creation with response error + """ + with self.assertRaises(RequestError): + self.cm.create_keypair("KeyPairCreationUser") + + def test_edit_bucket_template_error_response(self): + """ + Edit bucket template with error response + """ + with self.assertRaises(RequestError): + self.cm.edit_bucket_template("0") + + def test_edit_bucket_template_success(self): + """ + Successful modification of the default template + """ + response = self.cm.edit_bucket_template("5") + self.assertEqual(response.status_code, 200) + + def test_delete_user_inexistent_user(self): + """ + Deletion of a inexistent user + WARNING the curl command does not print + anything + """ + response = self.cm.delete_user("KeyPairUser") + self.assertEqual(response, None) + + def test_list_buckets_response_error(self): + """ + List buckets with response error + """ + self.patcher.stop() + self.patcher = patch( + "requests.request", self.req_mock.fake_request_only_failure + ) + self.patcher.start() + with self.assertRaises(RequestError): + self.cm.list_buckets() + + def test_create_user_response_error(self): + """ + Create user with response error + """ + with self.assertRaises(RequestError): + self.cm.create_user("ErroredUser") + + def test_add_bucket_acl_user_not_found_error(self): + """ + ACL addition to bucket with user not found + """ + with self.assertRaises(NotFoundError): + self.cm.add_bucket_acl("whateverName", "NotExistentName", "read-storage") + + def test_add_bucket_acl_bucket_not_found_error(self): + """ + ACL addition to bucket with bucket not found + """ + with self.assertRaises(NotFoundError): + self.cm.add_bucket_acl("NonExistent", "ResponseSuccess", "read-storage") + + def test_add_bucket_acl_success(self): + """ + Successful addition of ACL to bucket + """ + response = self.cm.add_bucket_acl( + "whateverName", "ResponseSuccess", ["read-storage"] + ) + self.assertEqual(response, None) + + def test_get_bucket_success(self): + """ + Successful retrieval of a bucket + """ + bucket = self.cm.get_bucket("testVaultName") + self.assertEqual(bucket.name, "testVaultName") + self.assertEqual(bucket.id, 274) + + def test_get_bucket_response_error(self): + """ + Test retrieval of an inexistent bucket + """ + with self.assertRaises(RequestError): + self.cm.get_bucket("InexistentBucket") + + def test_update_bucket_acl_success(self): + """ + Successful change of acl on a bucket + """ + response = self.cm.update_bucket_acl( + "testVaultName", [("ResponseSuccess", ["read-storage"])] + ) + self.assertEqual(response, None) + + def test_update_bucket_acl_error_response(self): + """ + Change of acl on a bucket with error response + """ + with self.assertRaises(RequestError): + self.cm.update_bucket_acl( + "testVaultName", [("KeyPairCreationUser", ["read-storage"])] + ) + + def test_delete_bucket_acl_success(self): + """ + Successful deletion of an acl + """ + response = self.cm.delete_bucket_acl("testVaultName", "ResponseSuccess") + self.assertEqual(response, None) + + def test_delete_bucket_acl_empty_name(self): + """ + Error handling when deleting an empty user from a bucket + """ + with self.assertRaises(RequestError): + self.cm.delete_bucket_acl("testVaultName", "") + + def test_delete_bucket_acl_empty_bucket(self): + """ + Error handling when deleting an empty bucket + """ + with self.assertRaises(RequestError): + self.cm.delete_bucket_acl("", "ResponseSuccess") diff --git a/tests/storageclient/test_google_api_client.py b/tests/storageclient/test_google_api_client.py new file mode 100644 index 000000000..38532b536 --- /dev/null +++ b/tests/storageclient/test_google_api_client.py @@ -0,0 +1,125 @@ +""" +Module for mocking and testing of the +google API client +""" +import pytest + +# Python 2 and 3 compatible +try: + from unittest.mock import MagicMock + from unittest.mock import patch +except ImportError: + from mock import MagicMock + from mock import patch + +from fence.resources.storage.storageclient.errors import RequestError, NotFoundError + + +class TestGoogleCloudStorageClient(object): + def test_client_creation(self, google_cloud_storage_client, test_cloud_manager): + """ + Ensure that a google project id gets populated + """ + assert google_cloud_storage_client.google_project_id == "test-google-project" + + def test_get_user_success(self, google_cloud_storage_client, test_cloud_manager): + """ + Successful retrieval of a user + """ + user_proxy = google_cloud_storage_client.get_user("user0") + assert getattr(user_proxy, "username") + + def test_get_user_nonexistent_user( + self, google_cloud_storage_client, test_cloud_manager + ): + """ + Retrieval of a nonexistent user + """ + user = google_cloud_storage_client.get_user("NonExistent") + assert user is None + + def test_add_bucket_acl_user_error( + self, google_cloud_storage_client, test_cloud_manager + ): + """ + ACL addition to bucket with user not found + """ + with pytest.raises(RequestError): + google_cloud_storage_client.add_bucket_acl( + access=["read-storage"], bucket="test_bucket", username="NonExistent" + ) + + def test_add_bucket_acl_bucket_error( + self, google_cloud_storage_client, test_cloud_manager + ): + """ + ACL addition to bucket with bucket not found + """ + with pytest.raises(RequestError): + google_cloud_storage_client.add_bucket_acl( + access=["read-storage"], + bucket="NonExistent", + username="user0_proxy_group@example.com", + ) + + def test_add_bucket_acl_success( + self, google_cloud_storage_client, test_cloud_manager + ): + """ + Successful addition of ACL to bucket + """ + response = google_cloud_storage_client.add_bucket_acl( + access=["read-storage"], + bucket="test_bucket", + username="user0_proxy_group@example.com", + ) + + # the response should contain the newly added email + assert response.get("email") == "user0_proxy_group@example.com" + + def test_add_bucket_acl_success_access( + self, google_cloud_storage_client, test_cloud_manager + ): + """ + Successful addition of ACL to bucket even when an access level is + supplied (should be ignored for Google) + """ + response = google_cloud_storage_client.add_bucket_acl( + access=["read-storage"], + bucket="test_bucket", + username="user0_proxy_group@example.com", + ) + + # the response should contain the newly added email + assert response.get("email") == "user0_proxy_group@example.com" + + def test_delete_bucket_acl_success( + self, google_cloud_storage_client, test_cloud_manager + ): + """ + Successful deletion of an acl + """ + response = google_cloud_storage_client.delete_bucket_acl( + bucket="test_bucket", user="user0_proxy_group@example.com" + ) + assert not response + + def test_delete_bucket_acl_empty_name( + self, google_cloud_storage_client, test_cloud_manager + ): + """ + Error handling when deleting an empty user from a bucket + """ + with pytest.raises(RequestError): + google_cloud_storage_client.delete_bucket_acl(bucket="test_bucket", user="") + + def test_delete_bucket_acl_empty_bucket( + self, google_cloud_storage_client, test_cloud_manager + ): + """ + Error handling when deleting an empty bucket + """ + with pytest.raises(RequestError): + google_cloud_storage_client.delete_bucket_acl( + bucket="", user="user0_proxy_group@example.com" + ) diff --git a/tests/storageclient/test_real_storage.py b/tests/storageclient/test_real_storage.py new file mode 100644 index 000000000..36ed13180 --- /dev/null +++ b/tests/storageclient/test_real_storage.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python + +import json +from fence.resources.storage.storageclient import CleversafeClient, errors +import unittest + + +# XXX: tests to fix +import pytest + +pytestmark = pytest.mark.skip + + +class TestStorage(unittest.TestCase): + @classmethod + def setUpClass(self): + with open("cred.json", "r") as f: + self.creds = json.load(f) + self.cc = CleversafeClient(self.creds) + self.test_user = self.cc.create_user("test_suite_user") + self.test_bucket = self.cc.create_bucket( + self.creds["aws_access_key_id"], + self.creds["aws_secret_access_key"], + "test_suite_bucket", + ) + + @classmethod + def tearDownClass(self): + self.cc.delete_user(self.test_user.username) + self.cc.delete_bucket(self.test_bucket.name) + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_create_list_and_delete_bucket(self): + """ + Successful creation, listing and deletion of a vault + """ + new_bucket_name = "my_new_tested_bucket" + self.cc.create_bucket( + self.creds["aws_access_key_id"], + self.creds["aws_secret_access_key"], + new_bucket_name, + ) + bucket = self.cc.get_bucket(new_bucket_name) + self.assertEqual(bucket.name, new_bucket_name) + suite_bucket_found = False + new_bucket_found = False + buckets = self.cc.list_buckets() + for buck in buckets: + if buck.name == new_bucket_name: + new_bucket_found = True + elif buck.name == self.test_bucket.name: + suite_bucket_found = True + self.assertTrue(new_bucket_found) + self.assertTrue(suite_bucket_found) + self.cc.delete_bucket(new_bucket_name) + with self.assertRaises(errors.RequestError): + self.cc.get_bucket(new_bucket_name) + + def test_create_list_and_delete_user(self): + """ + Successful creation, listing and deletion of a user + """ + new_user_name = "my_new_test_user" + self.cc.create_user(new_user_name) + user = self.cc.get_user(new_user_name) + self.assertEqual(user.username, new_user_name) + suite_user_found = False + new_user_found = False + users = self.cc.list_users() + for user in users: + if user.username == new_user_name: + new_user_found = True + elif user.username == self.test_user.username: + suite_user_found = True + self.assertTrue(new_user_found) + self.assertTrue(suite_user_found) + self.cc.delete_user(new_user_name) + user = self.cc.get_user(new_user_name) + self.assertEqual(user, None) + + def test_create_and_delete_keypair_success(self): + """ + Successful creation and deletion of keys + Check that the creation and deletion of + keys work. We check that we keep the same + status that we got at the start + """ + user = self.cc.get_user(self.test_user.username) + original_keys = user.keys + keypair = self.cc.create_keypair(user.username) + user = self.cc.get_user(self.test_user.username) + self.assertIn(keypair, user.keys) + keys = self.cc.delete_keypair(user.username, keypair["access_key"]) + user = self.cc.get_user(self.test_user.username) + self.assertEqual(user.keys, original_keys) + + def test_delete_keypair_inexistent_key(self): + """ + Error handling of inexistent user + """ + with self.assertRaises(errors.RequestError): + self.cc.delete_keypair(self.test_user.username, "inexistent_key") + + def test_set_bucket_quota_succes(self): + """ + Successful change of quota + """ + bucket = self.cc.get_bucket(self.test_bucket.name) + old_quota = bucket.quota + if old_quota != None: + MiB = old_quota / 1048576 + else: + MiB = 1 + self.cc.set_bucket_quota(self.test_bucket.name, "MiB", 2 * MiB) + bucket = self.cc.get_bucket(self.test_bucket.name) + self.assertEqual(bucket.quota / 1048576, MiB * 2) + + def test_delete_all_keypairs_success(self): + """ + Successful deletion of all keypairs + """ + user = self.cc.get_user(self.test_user.username) + original_keys = user.keys + keypair_1 = self.cc.create_keypair(user.username) + keypair_2 = self.cc.create_keypair(user.username) + user = self.cc.get_user(self.test_user.username) + self.assertIn(keypair_1, user.keys) + self.assertIn(keypair_2, user.keys) + keys = self.cc.delete_all_keypairs(user.username) + user = self.cc.get_user(self.test_user.username) + self.assertEqual(user.keys, []) + + def test_edit_bucket_template_success(self): + """ + Successful modification of a template + This method has no way of knowing if the + data on the template has changed, + so once checked once that it works, + it is here only to check that we haven't + broken the template + """ + self.cc.edit_bucket_template(1, description="This is a test description") + self.cc.edit_bucket_template(1, description="") + + def test_delete_user_inexistent_user(self): + """ + Error handling of deletion of an inexistent user + """ + with self.assertRaises(KeyError): + self.cc.delete_user("this_user_will_never_exist") + + def test_add_bucket_acl_user_not_found_error(self): + """ + Error handling of adding a bucket ACL on an inexistent user + """ + with self.assertRaises(errors.NotFoundError): + self.cc.add_bucket_acl( + self.test_bucket.name, "non_existent_user", ["read-storage"] + ) + + def test_add_bucket_acl_bucket_not_found_error(self): + """ + Error handling on adding a bucket ACL on an inexistent bucket + """ + with self.assertRaises(errors.NotFoundError): + self.cc.add_bucket_acl( + "non_existent_bucket", self.test_user.username, ["read-storage"] + ) + + def test_add_bucket_acl_success(self): + """ + Successful addition of an ACL on a bucket + This method has no way of retrieving the information + TODO add writing test on the bucket to check permissions + """ + self.cc.add_bucket_acl( + self.test_bucket.name, self.test_user.username, ["read-storage"] + ) + self.cc.add_bucket_acl( + self.test_bucket.name, self.test_user.username, ["disabled"] + ) + + def test_get_bucket_response_error(self): + """ + Error handling on getting an inexistent bucket + """ + with self.assertRaises(errors.RequestError): + self.cc.get_bucket("inexistent_bucket") + + def test_update_bucket_acl_success(self): + """ + Successful updating of a bucket ACL + This method has no way of retrieving the modified + information + TODO add a writing check + """ + self.cc.update_bucket_acl( + self.test_bucket.name, [(self.test_user.username, ["read-storage"])] + ) + self.cc.update_bucket_acl( + self.test_bucket.name, [(self.test_user.username, ["disabled"])] + ) diff --git a/tests/test-fence-config.yaml b/tests/test-fence-config.yaml index 086c4f6ac..38ccbd147 100755 --- a/tests/test-fence-config.yaml +++ b/tests/test-fence-config.yaml @@ -28,7 +28,7 @@ BASE_URL: 'http://localhost/user' # postgres db to connect to # connection url format: # postgresql://[user[:password]@][netloc][:port][/dbname] -DB: 'postgresql://postgres:postgres@localhost:5432/fence_test_tmp' +DB: 'postgresql://postgres:postgres@localhost:5432/postgres' # A URL-safe base64-encoded 32-byte key for encrypting keys in db # in python you can use the following script to generate one: @@ -120,6 +120,7 @@ OPENID_CONNECT: client_secret: '' redirect_url: '' fence: + name: 'fence IDP' client_id: '' client_secret: '' redirect_url: '{{BASE_URL}}/login/fence/login' @@ -232,16 +233,9 @@ LOGIN_OPTIONS: idp: generic2 # ////////////////////////////////////////////////////////////////////////////////////// -# LIBRARY CONFIGURATION (authlib & flask) +# LIBRARY CONFIGURATION (flask) # - Already contains reasonable defaults # ////////////////////////////////////////////////////////////////////////////////////// -# authlib-specific configs for OIDC flow and JWTs -# NOTE: the OAUTH2_JWT_KEY cfg gets set automatically by fence if keys are setup -# correctly -OAUTH2_JWT_ALG: 'RS256' -OAUTH2_JWT_ENABLED: true -OAUTH2_JWT_ISS: '{{BASE_URL}}' -OAUTH2_PROVIDER_ERROR_URI: '/api/oauth2/errors' # used for flask, "path mounted under by the application / web server" # since we deploy as microservices, fence is typically under {{base}}/user diff --git a/tests/test_audit_service.py b/tests/test_audit_service.py index b1d6568d9..cbf5c8cf2 100644 --- a/tests/test_audit_service.py +++ b/tests/test_audit_service.py @@ -450,7 +450,7 @@ def test_login_log_login_endpoint( elif idp == "fence": mocked_fetch_access_token = MagicMock(return_value={"id_token": jwt_string}) patch( - f"flask.current_app.fence_client.fetch_access_token", + f"authlib.integrations.flask_client.apps.FlaskOAuth2App.fetch_access_token", mocked_fetch_access_token, ).start() mocked_validate_jwt = MagicMock( @@ -490,7 +490,7 @@ def test_login_log_login_endpoint( data={}, status_code=201, ) - path = f"/login/{idp}/{callback_endpoint}" + path = f"/login/{idp}/{callback_endpoint}" # SEE fence/blueprints/login/fence_login.py L91 response = client.get(path, headers=headers) assert response.status_code == 200, response audit_service_requests.post.assert_called_once_with( diff --git a/tests/test_logout.py b/tests/test_logout.py index 49df98c6a..eb0f8f538 100644 --- a/tests/test_logout.py +++ b/tests/test_logout.py @@ -78,9 +78,10 @@ def test_logout_fence(app, client, user_with_fence_provider, monkeypatch): with mock.patch("fence.allowed_login_redirects", return_value={"some_site.com"}): # manually set cookie for initial session client.set_cookie( - "localhost", - config["SESSION_COOKIE_NAME"], - test_session_jwt, + key=config["SESSION_COOKIE_NAME"], + value=test_session_jwt, + # domain is used in client.get_cookie, it defaults to locahost anyway + domain="localhost", httponly=True, samesite="Lax", ) diff --git a/travis/pg_hba.conf b/travis/pg_hba.conf deleted file mode 100644 index e080219fd..000000000 --- a/travis/pg_hba.conf +++ /dev/null @@ -1,10 +0,0 @@ -# This config file will be used for the Travis test run. -# -# The new PostgreSQL 13 changes some settings from what they originally were -# in Travis, so we'll set them back. In particular we want to enable -# passwordless authentication for connections to PostgreSQL. -# Source: https://github.com/NCI-GDC/psqlgraph/blob/94f315db2c039217752cba85d9c63988f2059317/travis/pg_hba.conf -local all postgres trust -local all all trust -host all all 127.0.0.1/32 trust -host all all ::1/128 trust diff --git a/travis/postgresql.conf b/travis/postgresql.conf deleted file mode 100644 index d3959e564..000000000 --- a/travis/postgresql.conf +++ /dev/null @@ -1,32 +0,0 @@ -# This config file will be used for PostgreSQL 13 because Travis doesn't -# have configurations set up for it yet. The most important part will be the -# ramfs storage location change. It also defaults to port 5433 so we need to -# change that back, too. -# Copied from https://github.com/NCI-GDC/psqlgraph/blob/94f315db2c039217752cba85d9c63988f2059317/travis/postgresql.conf -data_directory = '/var/ramfs/postgresql/13/main' -hba_file = '/etc/postgresql/13/main/pg_hba.conf' -ident_file = '/etc/postgresql/13/main/pg_ident.conf' -external_pid_file = '/var/run/postgresql/13-main.pid' -port = 5432 -max_connections = 255 -unix_socket_directories = '/var/run/postgresql' -ssl = on -ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem' -ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key' -shared_buffers = 128MB -dynamic_shared_memory_type = posix -max_wal_size = 256MB -min_wal_size = 80MB -log_line_prefix = '%t ' -log_timezone = 'UTC' -cluster_name = '13/main' -stats_temp_directory = '/var/run/postgresql/13-main.pg_stat_tmp' -datestyle = 'iso, mdy' -timezone = 'UTC' -lc_messages = 'en_US.UTF-8' -lc_monetary = 'en_US.UTF-8' -lc_numeric = 'en_US.UTF-8' -lc_time = 'en_US.UTF-8' -default_text_search_config = 'pg_catalog.english' -include_dir = 'conf.d' -fsync = false