# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['authmethod', 'descope', 'descope.authmethod', 'descope.management']

package_data = \
{'': ['*']}

install_requires = \
['PyJWT==2.6.0',
 'cryptography==39.0.1',
 'email-validator==1.3.1',
 'requests==2.28.2']

setup_kwargs = {
    'name': 'descope',
    'version': '0.9.1',
    'description': 'Descope Python SDK',
    'long_description': '# Descope SDK for Python\n\nThe Descope SDK for python provides convenient access to the Descope user management and authentication API\nfor a backend written in python. You can read more on the [Descope Website](https://descope.com).\n\n## Requirements\n\nThe SDK supports Python 3.7 and above.\n\n## Installing the SDK\n\nInstall the package with:\n\n```bash\npip install descope\n```\n\n## Setup\n\nA Descope `Project ID` is required to initialize the SDK. Find it on the\n[project page in the Descope Console](https://app.descope.com/settings/project).\n\n```python\nfrom descope import DescopeClient\n\n# Initialized after setting the DESCOPE_PROJECT_ID env var\ndescope_client = DescopeClient()\n\n# ** Or directly **\ndescope_client = DescopeClient(project_id="<Project ID>")\n```\n\n## Usage\n\nHere are some examples how to manage and authenticate users:\n\n### OTP Authentication\n\nSend a user a one-time password (OTP) using your preferred delivery method (_email / SMS_). An email address or phone number must be provided accordingly.\n\nThe user can either `sign up`, `sign in` or `sign up or in`\n\n```python\nfrom descope import DeliveryMethod\n\n# Every user must have a login ID. All other user information is optional\nemail = "desmond@descope.com"\nuser = {"name": "Desmond Copeland", "phone": "212-555-1234", "email": email}\ndescope_client.otp.sign_up(method=DeliveryMethod.EMAIL, login_id=email, user=user)\n```\n\nThe user will receive a code using the selected delivery method. Verify that code using:\n\n```python\njwt_response = descope_client.otp.verify_code(\n    method=DeliveryMethod.EMAIL, login_id=email, code=value\n)\nsession_token = jwt_response[SESSION_TOKEN_NAME].get("jwt")\nrefresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt")\n```\n\nThe session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation)\n\n### Magic Link\n\nSend a user a Magic Link using your preferred delivery method (_email / SMS_).\nThe Magic Link will redirect the user to page where the its token needs to be verified.\nThis redirection can be configured in code, or generally in the [Descope Console](https://app.descope.com/settings/authentication/magiclink)\n\nThe user can either `sign up`, `sign in` or `sign up or in`\n\n```python\nfrom descope import DeliveryMethod\n\ndescope_client.magiclink.sign_up_or_in(\n    method=DeliveryMethod.EMAIL,\n    login_id="desmond@descope.com",\n    uri="http://myapp.com/verify-magic-link", # Set redirect URI here or via console\n)\n```\n\nTo verify a magic link, your redirect page must call the validation function on the token (`t`) parameter (`https://your-redirect-address.com/verify?t=<token>`):\n\n```python\njwt_response = descope_client.magiclink.verify(token=token)\nsession_token = jwt_response[SESSION_TOKEN_NAME].get("jwt")\nrefresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt")\n```\n\nThe session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation)\n\n### Enchanted Link\n\nUsing the Enchanted Link APIs enables users to sign in by clicking a link\ndelivered to their email address. The email will include 3 different links,\nand the user will have to click the right one, based on the 2-digit number that is\ndisplayed when initiating the authentication process.\n\nThis method is similar to [Magic Link](#magic-link) but differs in two major ways:\n\n- The user must choose the correct link out of the three, instead of having just one\n  single link.\n- This supports cross-device clicking, meaning the user can try to log in on one device,\n  like a computer, while clicking the link on another device, for instance a mobile phone.\n\nThe Enchanted Link will redirect the user to page where the its token needs to be verified.\nThis redirection can be configured in code per request, or set globally in the [Descope Console](https://app.descope.com/settings/authentication/enchantedlink).\n\nThe user can either `sign up`, `sign in` or `sign up or in`\n\n```python\nresp = descope_client.enchantedlink.sign_up_or_in(\n    login_id=email,\n    uri="http://myapp.com/verify-enchanted-link", # Set redirect URI here or via console\n)\nlink_identifier = resp["linkId"] # Show the user which link they should press in their email\npending_ref = resp["pendingRef"] # Used to poll for a valid session\n```\n\nAfter sending the link, you must poll to receive a valid session using the `pending_ref` from\nthe previous step. A valid session will be returned only after the user clicks the right link.\n\n```python\ni = 0\nwhile not done and i < max_tries:\n    try:\n        i = i + 1\n        sleep(4)\n        jwt_response = descope_client.enchantedlink.get_session(pending_ref)\n        done = True\n    except AuthException as e: # Poll while still receiving 401 Unauthorized\n        if e.status_code != 401: # Other failures means something\'s wrong, abort\n            logging.info(f"Failed pending session, err: {e}")\n            done = True\n\nif jwt_response:\n    session_token = jwt_response[SESSION_TOKEN_NAME].get("jwt")\n    refresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt")\n```\n\nTo verify an enchanted link, your redirect page must call the validation function on the token (`t`) parameter (`https://your-redirect-address.com/verify?t=<token>`). Once the token is verified, the session polling will receive a valid `jwt_response`.\n\n```python\ntry:\n    descope_client.enchantedlink.verify(token=token)\n    # Token is valid\nexcept AuthException as e:\n    # Token is invalid\n```\n\nThe session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation)\n\n### OAuth\n\nUsers can authenticate using their social logins, using the OAuth protocol. Configure your OAuth settings on the [Descope console](https://app.descope.com/settings/authentication/social). To start a flow call:\n\n```python\n\ndescope_client.oauth.start(\n    provider="google", # Choose an oauth provider out of the supported providers\n    return_url="https://my-app.com/handle-oauth", # Can be configured in the console instead of here\n)\n```\n\nThe user will authenticate with the authentication provider, and will be redirected back to the redirect URL, with an appended `code` HTTP URL parameter. Exchange it to validate the user:\n\n```python\njwt_response = descope_client.oauth.exchange_token(code)\nsession_token = jwt_response[SESSION_TOKEN_NAME].get("jwt")\nrefresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt")\n```\n\nThe session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation)\n\n### SSO/SAML\n\nUsers can authenticate to a specific tenant using SAML or Single Sign On. Configure your SSO/SAML settings on the [Descope console](https://app.descope.com/settings/authentication/sso). To start a flow call:\n\n```python\n\ndescope_client.saml.start(\n    tenant="my-tenant-ID", # Choose which tenant to log into\n    return_url="https://my-app.com/handle-saml", # Can be configured in the console instead of here\n)\n```\n\nThe user will authenticate with the authentication provider configured for that tenant, and will be redirected back to the redirect URL, with an appended `code` HTTP URL parameter. Exchange it to validate the user:\n\n```python\njwt_response = descope_client.saml.exchange_token(code)\nsession_token = jwt_response[SESSION_TOKEN_NAME].get("jwt")\nrefresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt")\n```\n\nThe session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation)\n\n### TOTP Authentication\n\nThe user can authenticate using an authenticator app, such as Google Authenticator.\nSign up like you would using any other authentication method. The sign up response\nwill then contain a QR code `image` that can be displayed to the user to scan using\ntheir mobile device camera app, or the user can enter the `key` manually or click\non the link provided by the `provisioning_url`.\n\nExisting users can add TOTP using the `update` function.\n\n```python\nfrom descope import DeliveryMethod\n\n# Every user must have a login ID. All other user information is optional\nemail = "desmond@descope.com"\nuser = {"name": "Desmond Copeland", "phone": "212-555-1234", "email": email}\ntotp_response = descope_client.totp.sign_up(method=DeliveryMethod.EMAIL, login_id=email, user=user)\n\n# Use one of the provided options to have the user add their credentials to the authenticator\nprovisioning_url = totp_response["provisioningURL"]\nimage = totp_response["image"]\nkey = totp_response["key"]\n```\n\nThere are 3 different ways to allow the user to save their credentials in\ntheir authenticator app - either by clicking the provisioning URL, scanning the QR\nimage or inserting the key manually. After that, signing in is done using the code\nthe app produces.\n\n```python\njwt_response = descope_client.totp.sign_in_code(\n    login_id=email,\n    code=code, # Code from authenticator app\n)\nsession_token = jwt_response[SESSION_TOKEN_NAME].get("jwt")\nrefresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt")\n```\n\nThe session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation)\n\n### Session Validation\n\nEvery secure request performed between your client and server needs to be validated. The client sends\nthe session and refresh tokens with every request, and they are validated using one of the following:\n\n```python\n# Validate the session. Will raise if expired\ntry:\n    descope_client.validate_session(session_token)\nexcept AuthException:\n    # Session expired\n\n# If validate_session raises an exception, you will need to refresh the session using\njwt_response = descope_client.refresh_session(refresh_token)\n\n# Alternatively, you could combine the two and\n# have the session validated and automatically refreshed when expired\njwt_response = descope_client.validate_and_refresh_session(session_token, refresh_token)\n```\n\nChoose the right session validation and refresh combination that suits your needs.\n\nRefreshed sessions return the same response as is returned when users first sign up / log in,\ncontaining the session and refresh tokens, as well as all of the JWT claims.\nMake sure to return the tokens from the response to the client, or updated the cookie if you\'re using it.\n\nUsually, the tokens can be passed in and out via HTTP headers or via a cookie.\nThe implementation can defer according to your framework of choice. See our [samples](#code-samples) for a few examples.\n\nIf Roles & Permissions are used, validate them immediately after validating the session. See the [next section](#roles--permission-validation)\nfor more information.\n\n### Roles & Permission Validation\n\nWhen using Roles & Permission, it\'s important to validate the user has the required\nauthorization immediately after making sure the session is valid. Taking the `jwt_response`\nreceived by the [session validation](#session-validation), call the following functions:\n\nFor multi-tenant uses:\n\n```python\n# You can validate specific permissions\nvalid_permissions = descope_client.validate_tenant_permissions(\n    jwt_response, "my-tenant-ID", ["Permission to validate"]\n)\nif not valid_permissions:\n    # Deny access\n\n# Or validate roles directly\nvalid_roles = descope_client.validate_tenant_roles(\n    jwt_response, "my-tenant-ID", ["Role to validate"]\n)\nif not valid_roles:\n    # Deny access\n```\n\nWhen not using tenants use:\n\n```python\n# You can validate specific permissions\nvalid_permissions = descope_client.validate_permissions(\n    jwt_response, ["Permission to validate"]\n)\nif not valid_permissions:\n    # Deny access\n\n# Or validate roles directly\nvalid_roles = descope_client.validate_roles(\n    jwt_response, ["Role to validate"]\n)\nif not valid_roles:\n    # Deny access\n```\n\n### Logging Out\n\nYou can log out a user from an active session by providing their `refresh_token` for that session.\nAfter calling this function, you must invalidate or remove any cookies you have created.\n\n```python\ndescope_client.logout(refresh_token)\n```\n\nIt is also possible to sign the user out of all the devices they are currently signed-in with. Calling `logout_all` will\ninvalidate all user\'s refresh tokens. After calling this function, you must invalidate or remove any cookies you have created.\n\n```python\ndescope_client.logout_all(refresh_token)\n```\n\n## Management API\n\nIt is very common for some form of management or automation to be required. These can be performed\nusing the management API. Please note that these actions are more sensitive as they are administrative\nin nature. Please use responsibly.\n\n### Setup\n\nTo use the management API you\'ll need a `Management Key` along with your `Project ID`.\nCreate one in the [Descope Console](https://app.descope.com/settings/company/managementkeys).\n\n```python\nfrom descope import DescopeClient\n\n# Initialized after setting the DESCOPE_PROJECT_ID and the DESCOPE_MANAGEMENT_KEY env vars\ndescope_client = DescopeClient()\n\n# ** Or directly **\ndescope_client = DescopeClient(project_id="<Project ID>", management_key="<Management Key>")\n```\n\n### Manage Tenants\n\nYou can create, update, delete or load tenants:\n\n```Python\n# You can optionally set your own ID when creating a tenant\ndescope_client.mgmt.tenant.create(\n    name="My First Tenant",\n    id="my-custom-id", # This is optional. If omitted\n    self_provisioning_domains=["domain.com"],\n)\n\n# Update will override all fields as is. Use carefully.\ndescope_client.mgmt.tenant.update(\n    id="my-custom-id",\n    name="My First Tenant",\n    self_provisioning_domains=["domain.com", "another-domain.com"],\n)\n\n# Tenant deletion cannot be undone. Use carefully.\ndescope_client.mgmt.tenant.delete("my-custom-id")\n\n# Load all tenants\ntenants_resp = descope_client.mgmt.tenant.load_all()\ntenants = tenants_resp["tenants"]\n    for tenant in tenants:\n        # Do something\n```\n\n### Manage Users\n\nYou can create, update, delete or load users, as well as search according to filters:\n\n```Python\n# A user must have a login ID, other fields are optional.\n# Roles should be set directly if no tenants exist, otherwise set\n# on a per-tenant basis.\ndescope_client.mgmt.user.create(\n    login_id="desmond@descope.com",\n    email="desmond@descope.com",\n    display_name="Desmond Copeland",\n    user_tenants=[\n        AssociatedTenant("my-tenant-id", ["role-name1"]),\n    ],\n)\n\n# Update will override all fields as is. Use carefully.\ndescope_client.mgmt.user.update(\n    login_id="desmond@descope.com",\n    email="desmond@descope.com",\n    display_name="Desmond Copeland",\n    user_tenants=[\n        AssociatedTenant("my-tenant-id", ["role-name1", "role-name2"]),\n    ],\n)\n\n# Update explicit data for a user rather than overriding all fields\ndescope_client.mgmt.user.update_phone(\n    login_id="desmond@descope.com",\n    phone="+18005551234",\n    verified=True,\n)\ndescope_client.mgmt.user.remove_tenant_roles(\n    login_id="desmond@descope.com",\n    tenant_id="my-tenant-id",\n    role_names=["role-name1"],\n)\n\n# User deletion cannot be undone. Use carefully.\ndescope_client.mgmt.user.delete("desmond@descope.com")\n\n# Load specific user\nuser_resp = descope_client.mgmt.user.load("desmond@descope.com")\nuser = user_resp["user"]\n\n# If needed, users can be loaded using the user ID as well\nuser_resp = descope_client.mgmt.user.load_by_user_id("<user-id>")\nuser = user_resp["user"]\n\n# Search all users, optionally according to tenant and/or role filter\nusers_resp = descope_client.mgmt.user.search_all(tenant_ids=["my-tenant-id"])\nusers = users_resp["users"]\n    for user in users:\n        # Do something\n```\n\n### Manage Access Keys\n\nYou can create, update, delete or load access keys, as well as search according to filters:\n\n```Python\n# An access key must have a name and expiration, other fields are optional.\n# Roles should be set directly if no tenants exist, otherwise set\n# on a per-tenant basis.\ncreate_resp = descope_client.mgmt.access_key.create(\n    name="name",\n    expired_time=1677844931,\n    key_tenants=[\n        AssociatedTenant("my-tenant-id", ["role-name1"]),\n    ],\n)\nkey = create_resp["key"]\ncleartext = create_resp["cleartext"] # make sure to save the returned cleartext securely. It will not be returned again.\n\n# Load a specific access key\naccess_key_resp = descope_client.mgmt.access_key.load("key-id")\naccess_key = access_key_resp["key"]\n\n# Search all access keys, optionally according to a tenant filter\nkeys_resp = descope_client.mgmt.access_key.search_all(tenant_ids=["my-tenant-id"])\nkeys = keys_resp["keys"]\n    for key in keys:\n        # Do something\n\n# Update will override all fields as is. Use carefully.\ndescope_client.mgmt.access_key.update(\n    id="key-id",\n    name="new name",\n)\n\n# Access keys can be deactivated to prevent usage. This can be undone using "activate".\ndescope_client.mgmt.access_key.deactivate("key-id")\n\n# Disabled access keys can be activated once again.\ndescope_client.mgmt.access_key.activate("key-id")\n\n# Access key deletion cannot be undone. Use carefully.\ndescope_client.mgmt.access_key.delete("key-id")\n```\n\n### Manage SSO Setting\n\nYou can manage SSO settings and map SSO group roles and user attributes.\n\n```Python\n# You can configure SSO settings manually by setting the required fields directly\ndescope_client.mgmt.sso.configure(\n    tenant_id, # Which tenant this configuration is for\n    idp_url="https://idp.com",\n    entity_id="my-idp-entity-id",\n    idp_cert="<your-cert-here>",\n)\n\n# Alternatively, configure using an SSO metadata URL\ndescope_client.mgmt.sso.configure_via_metadata(\n    tenant_id, # Which tenant this configuration is for\n    idp_metadata_url="https://idp.com/my-idp-metadata",\n)\n\n# Map IDP groups to Descope roles, or map user attributes.\n# This function overrides any previous mapping (even when empty). Use carefully.\ndescope_client.mgmt.sso.mapping(\n    tenant_id, # Which tenant this mapping is for\n    role_mappings = [RoleMapping(["IDP_ADMIN"], "Tenant Admin")],\n    attribute_mapping=AttributeMapping(name="IDP_NAME", phone_number="IDP_PHONE"),\n)\n```\n\nNote: Certificates should have a similar structure to:\n\n```\n-----BEGIN CERTIFICATE-----\nCertifcate contents\n-----END CERTIFICATE-----\n```\n\n### Manage Permissions\n\nYou can create, update, delete or load permissions:\n\n```Python\n# You can optionally set a description for a permission.\ndescope_client.mgmt.permission.create(\n    name="My Permission",\n    description="Optional description to briefly explain what this permission allows."\n)\n\n# Update will override all fields as is. Use carefully.\ndescope_client.mgmt.permission.update(\n    name="My Permission",\n    new_name="My Updated Permission",\n    description="A revised description"\n)\n\n# Permission deletion cannot be undone. Use carefully.\ndescope_client.mgmt.permission.delete("My Updated Permission")\n\n# Load all permissions\npermissions_resp = descope_client.mgmt.permission.load_all()\npermissions = permissions_resp["permissions"]\n    for permission in permissions:\n        # Do something\n```\n\n### Manage Roles\n\nYou can create, update, delete or load roles:\n\n```Python\n# You can optionally set a description and associated permission for a roles.\ndescope_client.mgmt.role.create(\n    name="My Role",\n    description="Optional description to briefly explain what this role allows.",\n    permission_names=["My Updated Permission"],\n)\n\n# Update will override all fields as is. Use carefully.\ndescope_client.mgmt.role.update(\n    name="My Role",\n    new_name="My Updated Role",\n    description="A revised description",\n    permission_names=["My Updated Permission", "Another Permission"]\n)\n\n# Role deletion cannot be undone. Use carefully.\ndescope_client.mgmt.role.delete("My Updated Role")\n\n# Load all roles\nroles_resp = descope_client.mgmt.role.load_all()\nroles = roles_resp["roles"]\n    for role in roles:\n        # Do something\n```\n\n### Query SSO Groups\n\nYou can query SSO groups:\n\n```Python\n# Load all groups for a given tenant id\ngroups_resp = descope_client.mgmt.group.load_all_groups(\n    tenant_id="tenant-id",\n)\n\n# Load all groups for the given user IDs (can be found in the user\'s JWT)\ngroups_resp = descope_client.mgmt.group.load_all_groups_for_members(\n    tenant_id="tenant-id",\n    user_ids=["user-id-1", "user-id-2"],\n)\n\n# Load all groups for the given user\'s login IDs (used for sign-in)\ngroups_resp = descope_client.mgmt.group.load_all_groups_for_members(\n    tenant_id="tenant-id",\n    login_ids=["login-id-1", "login-id-2"],\n)\n\n# Load all group\'s members by the given group id\ngroups_resp = descope_client.mgmt.group.load_all_group_members(\n    tenant_id="tenant-id",\n    group_id="group-id,\n)\n\nfor group in groups_resp:\n    # Do something\n```\n\n### Manage JWTs\n\nYou can add custom claims to a valid JWT.\n\n```python\nupdated_jwt = client.mgmt.jwt.updateJWT(\n    jwt: "original-jwt",\n    custom_claims: {\n        "custom-key1": "custom-value1",\n        "custom-key2": "custom-value2",\n    },\n)\n```\n\n## API Rate limits\n\nHandle API rate limits by comparing the exception to the APIRateLimitExceeded exception, which includes the RateLimitParameters map with the key "Retry-After." This key indicates how many seconds until the next valid API call can take place. More information on Descope\'s rate limit is covered here: [Descope rate limit reference page](https://docs.descope.com/rate-limit)\n\n```python\ntry:\n    descope_client.magiclink.sign_up_or_in(\n        method=DeliveryMethod.EMAIL,\n        login_id="desmond@descope.com",\n        uri="http://myapp.com/verify-magic-link",\n    )\nexcept RateLimitException as e:\n    retry_after_seconds = e.rate_limit_parameters.get(API_RATE_LIMIT_RETRY_AFTER_HEADER)\n    # This variable indicates how many seconds until the next valid API call can take place.\n```\n\n\n## Code Samples\n\nYou can find various usage samples in the [samples folder](https://github.com/descope/python-sdk/blob/main/samples).\n\n## Learn More\n\nTo learn more please see the [Descope Documentation and API reference page](https://docs.descope.com/).\n\n## Contact Us\n\nIf you need help you can email [Descope Support](mailto:support@descope.com)\n\n## License\n\nThe Descope SDK for Python is licensed for use under the terms and conditions of the [MIT license Agreement](https://github.com/descope/python-sdk/blob/main/LICENSE).\n',
    'author': 'Descope',
    'author_email': 'info@descope.com',
    'maintainer': 'None',
    'maintainer_email': 'None',
    'url': 'https://descope.com/',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.7,<4.0',
}


setup(**setup_kwargs)
