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

packages = \
['flask_azure_oauth', 'flask_azure_oauth.mocks']

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

install_requires = \
['Flask>=1.1.2,<2.0.0', 'authlib>=0.14.1,<0.15.0', 'requests>=2.23.0,<3.0.0']

setup_kwargs = {
    'name': 'flask-azure-oauth',
    'version': '0.5.0',
    'description': 'Python Flask extension for using Azure Active Directory with OAuth to protect applications',
    'long_description': '# Flask Azure AD OAuth Provider\n\nPython Flask extension for securing apps with Azure Active Directory OAuth\n\n## Purpose\n\nThis provider defines an [AuthLib](https://authlib.org)\n[Resource Protector](https://docs.authlib.org/en/latest/flask/2/resource-server.html) to authenticate and authorise\nusers and other applications to access features or resources within a Flask application using the OAuth functionality\noffered by [Azure Active Directory](https://azure.microsoft.com/en-us/services/active-directory/), as part of the\n[Microsoft identity platform](https://docs.microsoft.com/en-us/azure/active-directory/develop/about-microsoft-identity-platform).\n\nThis provider depends on Azure Active Directory, which acts as a identity provider, to issue\n[OAuth access tokens](https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens). These contain\nvarious claims including the identity of the user and client application (used for authentication) and any permissions\nassigned or delegated to the user or application (used for authorisation).\n\nThis provider will validate and interpret information in these tokens to restrict access to parts of a Flask app.\n\nSpecifically this provider supports these scenarios:\n\n1. *application to application*\n   * supports authentication and authorisation\n   * used to allow a client application access to some functionality or resources provided by another application\n   * can be used for non-interactive, machine-to-machine, processes (using the OAuth Client Credentials Grant)\n   * optionally, uses the identity of the client application for authentication\n   * optionally, uses permissions assigned directly to the client application for authorisation\n2. *user to application*\n    * supports authentication and authorisation\n    * used to allow users access to some functionality or resources provided by another application\n    * can be used for interactive console (using the Device Authorization Grant) or web application (using the OAuth\n      Authorization Code Grant) processes\n    * uses the identity of the user, and optionally, the client application they are using, for authentication\n    * optionally, uses permissions assigned to the user, permissions delegated by the user to the client application,\n      and/or permissions assigned directly to the client application for authorisation\n\nOther scenarios may be added in future versions of this provider.\n\n**Note:** This provider does not support client applications requesting tokens from Azure. See the\n[Microsoft Authentication Library (MSAL) for Python](https://github.com/AzureAD/microsoft-authentication-library-for-python)\npackage if you need to do this.\n\n## Installation\n\nThis package can be installed using Pip from [PyPi](https://pypi.org/project/flask-azure-oauth):\n\n```\n$ pip install flask-azure-oauth\n```\n\n## Usage\n\nThis provider provides an [AuthLib](https://authlib.org)\n[Resource Protector](https://docs.authlib.org/en/latest/flask/2/resource-server.html) which can be used as a decorator\non Flask routes.\n\nA minimal application would look like this:\n\n```python\nfrom flask import Flask\n\nfrom flask_azure_oauth import FlaskAzureOauth\n\napp = Flask(__name__)\n\napp.config[\'AZURE_OAUTH_TENANCY\'] = \'xxx\'\napp.config[\'AZURE_OAUTH_APPLICATION_ID\'] = \'xxx\'\n\nauth = FlaskAzureOauth()\nauth.init_app(app)\n\n@app.route(\'/unprotected\')\ndef unprotected():\n    return \'hello world\'\n\n@app.route(\'/protected\')\n@auth()\ndef protected():\n    return \'hello authenticated entity\'\n\n@app.route(\'/protected-with-single-scope\')\n@auth(\'required-scope\')\ndef protected_with_scope():\n    return \'hello authenticated and authorised entity\'\n\n@app.route(\'/protected-with-multiple-scopes\')\n@auth(\'required-scope1 required-scope2\')\ndef protected_with_multiple_scopes():\n    return \'hello authenticated and authorised entity\'\n```\n\nTo restrict a route to any valid user or client application (authentication):\n\n* add the resource protector as a decorator (`auth` in this example) - for example the `/protected` route\n\nTo restrict a route to specific users (authorisation):\n\n* add any required [Scopes](#permissions-roles-and-scopes) to the decorator - for example the `/projected-with-*` routes\n\nIndependently of these options, it\'s possible to require specific, trusted, client applications, regardless of the user \nusing them. This is useful in circumstances where a user may be authorised but the client can\'t be trusted:\n\n* set the `AZURE_OAUTH_CLIENT_APPLICATION_IDS` config option to a list of Azure application identifiers\n\nFor example:\n\n```\napp.config[\'AZURE_OAUTH_CLIENT_APPLICATION_IDS\'] = [\'xxx\']`\n```\n\n### Configuration options\n\nThe resource protector requires two configuration options to validate tokens correctly. These are read from the Flask\n[config object](http://flask.pocoo.org/docs/1.0/config/) through the `init_app()` method.\n\n| Configuration Option                 | Data Type | Required | Description                                                                                                                |\n| ------------------------------------ | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |\n| `AZURE_OAUTH_TENANCY`                | Str       | Yes      | ID of the Azure AD tenancy all applications and users are registered within                                                |\n| `AZURE_OAUTH_APPLICATION_ID`         | Str       | Yes      | ID of the Azure AD application registration for the application being protected                                            |\n| `AZURE_OAUTH_CLIENT_APPLICATION_IDS` | List[Str] | No       | ID(s) of the Azure AD application registration(s) for the application(s) granted access to the application being protected |\n\n**Note:** If the `AZURE_OAUTH_CLIENT_APPLICATION_IDS` option is not set, all client applications will be trusted and the \n`azp` claim, if present, is ignored.\n\nBefore these options can be set you will need to:\n\n1. [register the application to be protected](#registering-an-application-in-azure)\n2. [define the permissions and roles this application supports](#defining-permissions-and-roles-within-an-application)\n3. [register the application(s) that will use the protected application](#registering-an-application-in-azure)\n4. [assign permissions to users and/or client application(s)](#assigning-permissions-and-roles-to-users-and-applications)\n\n### Flask session support\n\nThis provider extends the AuthLib ResourceProtector to support detecting access tokens stored in the Flask session. \n\nThis is intended for browser based applications where the `Authorization` header cannot be easily set to include the\naccess token. This support will be enabled automatically if an `access_token` session key is set.\n\n### Access token versions\n\nSince version 0.5.0, this provider is compatible with Azure access token versions 1.0 and 2.0. Prior to version 0.5.0 \nonly version 2.0 tokens could be used. See \n[Microsoft\'s documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens) for the \ndifferences between token versions.\n\n**Note:** If you use version 1.0 tokens, this provider expects at least one of the `identifierUris` property values to \nbe `api://{protected_application_id}`, where `{protected_application_id}` is the application ID of the app registration\nrepresenting the application being protected by this provider. Without this, you will receive errors for an invalid \naudience.\n\n### Applications, users, groups and tenancies\n\nAzure Active Directory has a number of different concepts for agents that represent things being protected and things\nthat want to interact with protected things:\n\n* [applications](https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-scenarios#application-model) -\n  represent services that offer, or wish to use, functionality that should be restricted:\n    * services offering functionality are *protected applications*, e.g. an API\n    * services wishing to use functionality interactively or non-interactively, are *client applications*:\n        * interactive client applications include self-service portals for example\n         * non-interactive client applications include nightly synchronisation tasks for example\n* [users](https://docs.microsoft.com/en-us/azure/active-directory/users-groups-roles/directory-overview-user-model) -\n  represent individuals that wish to use functionality offered by protected applications, through one or more\n  client applications (e.g. a user may use a self-service portal to access information)\n* [groups](https://docs.microsoft.com/en-us/azure/active-directory/users-groups-roles/directory-overview-user-model) -\n  represent multiple users, for ease of managing permissions to similar users (e.g. administrative users)\n\nFor management purposes, all agents are scoped to an Azure tenancy (with the exception of users that can be used across\ntenancies).\n\nIn the Azure management portal:\n\n* applications are represented by *Application registrations*\n* users are represented by *users*, or optionally *groups* of users\n\n### Permissions, roles and scopes\n\nAzure Active Directory has a number of mechanisms for controlling how agents can interact with each other:\n\n* [roles](https://docs.microsoft.com/en-us/azure/architecture/multitenant-identity/app-roles) - functions, designations\n  or labels conferred on users and/or groups (e.g. `admins`, `staff`)\n* [direct permissions](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent) -\n  capabilities of a protected application client applications can use themselves or without the consent of the current\n  user (e.g. machine-to-machine access to, or modification of, data from all users)\n* [delegated permissions](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent) -\n  capabilities of a protected application the current user allows a client application to use (e.g. interactive access\n  to, or modification of, their data)\n\nGenerally, and in terms of the OAuth ecosystem, all of these can be considered as\n[scopes](https://tools.ietf.org/html/rfc6749#section-3.3). As discussed in the [Usage](#usage) section, scopes can be\nused to control who and/or what can use features within protected applications.\n\nScopes are included the access token generated by a client application (possibly interactively by a user) and presented\nto the projected application as a bearer token. Azure encodes different mechanisms in different claims:\n\n* `roles` - for roles assigned to users and permissions directly assigned to client applications\n* `scp` - for permissions delegated by the user to a client application\n\nFor ease of use, this extension abstracts these two claims into a single set of `scopes` that can be required for a\ngiven route. Multiple scopes can be required (as a logical AND) to allow scopes to be used more flexibly.\n\n#### Defining permissions and roles within an application\n\nPermissions and roles are defined in the\n[application manifest](https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-app-manifest) of each\napplication being protected. They can then be [assigned](#assigning-permissions-and-roles-to-users-and-applications) to\nusers, groups and client applications.\n\n1. [register](#registering-an-application-in-azure) the application to be protected\n2. [add permissions to application manifest](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps)\n\nFor example:\n\n```json\n"appRoles": [\n  {\n    "allowedMemberTypes": [\n      "Application"\n    ],\n    "displayName": "List all Foo resources",\n    "id": "112b3a76-2dd0-4d09-9976-9f94b2ed965d",\n    "isEnabled": true,\n    "description": "Allows access to basic information for all Foo resources",\n    "value": "Foo.List.All"\n  }\n],\n```\n\n#### Assigning permissions and roles to users and applications\n\nPermissions and roles (collectively, application roles) are assigned through the Azure portal:\n\n1. [define roles and permissions in the protected application](#defining-permissions-and-roles-within-an-application)\n2. [register](#registering-an-application-in-azure) the client application(s)\n3. assign:\n    * [roles to users/groups](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps)\n    * [permissions to client applications](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow#request-the-permissions-in-the-app-registration-portal)\n\nFor assigning permissions:\n\n* permissions can be delegated to client applications, with the agreement of the current user\n* permissions can be directly assigned to client applications, with the agreement of a tenancy administrator\n\n**Note:** Direct assignment is needed for non-interactive applications, such as daemons.\n\n#### Registering an application in Azure\n\n[Follow these instructions](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app).\n\n**Note:** These instructions apply both to applications that protected by this provider (protected applications), and\nthose that will be granted access to use such applications, possibly by a user (client applications).\n\n### Testing support\n\nFor testing applications, a local/test JSON Web Key Set (JWKS) can be used to sign local/test JSON Web Tokens (JWTs) \nwithout relying on Azure. Local tokens can include, or not include, arbitrary scopes/roles, which can ensure \nrequirements for specific scopes are properly enforced by this provider.\n\nThis requires using local tokens signed by the test keys, and patching the `FlaskAzureOauth._get_jwks` method to \nvalidate tokens using the same test keys. \n\nFor example:\n\n```python\nimport unittest\n\nfrom http import HTTPStatus\nfrom unittest.mock import patch\n\nfrom flask_azure_oauth import FlaskAzureOauth\nfrom flask_azure_oauth.mocks.keys import TestJwk\nfrom flask_azure_oauth.mocks.tokens import TestJwt\n\nfrom examples import create_app\n\n\nclass AppTestCase(unittest.TestCase):\n    def setUp(self):\n        self.test_jwks = TestJwk()\n\n        with patch.object(FlaskAzureOauth, "_get_jwks") as mocked_get_jwks:\n            mocked_get_jwks.return_value = self.test_jwks.jwks()\n\n            # `self.app` should be set to a Flask application, either by direct import, or by calling an app factory\n            self.app = create_app()\n\n            self.app.config["TEST_JWKS"] = self.test_jwks\n            self.app_context = self.app.app_context()\n            self.app_context.push()\n            self.client = self.app.test_client()\n\n    def test_protected_route_with_multiple_scopes_authorised(self):\n        # Generate token with required roles\n        token = TestJwt(\n            app=self.app, roles=["BAS.MAGIC.ADD.Records.Publish.All", "BAS.MAGIC.ADD.Records.ReadWrite.All"]\n        )\n\n        # Make request to protected route with token\n        response = self.client.get(\n            "/protected-with-multiple-scopes", headers={"authorization": f"bearer { token.dumps() }"}\n        )\n        self.assertEqual(HTTPStatus.OK, response.status_code)\n        self.app_context.pop()\n\n    def test_protected_route_with_multiple_scopes_unauthorised(self):\n        # Generate token with no scopes\n        token = TestJwt(app=self.app)\n\n        # Make request to protected route with token\n        response = self.client.get(\n            "/protected-with-multiple-scopes", headers={"authorization": f"bearer { token.dumps() }"}\n        )\n        self.assertEqual(HTTPStatus.FORBIDDEN, response.status_code)\n        self.app_context.pop()\n```\n\n## Developing\n\nThis project is developed as a Python library. A bundled Flask application is used to simulate its usage and to act as\nframework for running tests etc.\n\n```shell\n$ git clone https://gitlab.data.bas.ac.uk/web-apps/flask-extensions/flask-azure-oauth.git\n$ cd flask-azure-oauth\n```\n\n### Development environment\n\nDocker and Docker Compose are required to setup a local development environment of this application.\n\nIf you have access to the [BAS GitLab instance](https://gitlab.data.bas.ac.uk), you can pull the application Docker\nimage from the BAS Docker Registry. Otherwise you will need to build the Docker image locally.\n\n```shell\n# If you have access to gitlab.data.bas.ac.uk:\n$ docker login docker-registry.data.bas.ac.uk\n$ docker-compose pull\n# If you don\'t have access:\n$ docker-compose build\n```\n\n### Code Style\n\nPEP-8 style and formatting guidelines must be used for this project, with the exception of the 80 character line limit.\n\n[Black](https://github.com/psf/black) is used to ensure compliance, configured in `pyproject.toml`.\n\nBlack can be [integrated](https://black.readthedocs.io/en/stable/editor_integration.html#pycharm-intellij-idea) with a\nrange of editors, such as PyCharm, to perform formatting automatically.\n\nTo apply formatting manually:\n\n```shell\n$ docker-compose run app black flask_azure_oauth/\n```\n\nTo check compliance manually:\n\n```shell\n$ docker-compose run app black --check flask_azure_oauth/\n```\n\nChecks are ran automatically in [Continuous Integration](#continuous-integration).\n\n### Dependencies\n\nPython dependencies for this project are managed with [Poetry](https://python-poetry.org) in `pyproject.toml`.\n\nNon-code files, such as static files, can also be included in the [Python package](#python-package) using the\n`include` key in `pyproject.toml`.\n\n#### Adding new dependencies\n\nTo add a new (development) dependency:\n\n```shell\n$ docker-compose run app ash\n$ poetry add [dependency] (--dev)\n```\n\nThen rebuild the development container, and if you can, push to GitLab:\n\n```shell\n$ docker-compose build app\n$ docker-compose push app\n```\n\n#### Updating dependencies\n\nTo update dependencies:\n\n```shell\n$ docker-compose run app ash\n$ poetry update\n```\n\nThen rebuild the development container, and if you can, push to GitLab:\n\n```shell\n$ docker-compose build app\n$ docker-compose push app\n```\n\n### Supported Python versions\n\nThis project is only tested against the Python version used in the project container.\n\nOther Python versions may be compatible with this project but these are not tested or officially supported.\n\nA minimum Python version is set in `pyproject.toml`.\n\n### Static security scanning\n\nTo ensure the security of this API, source code is checked against [Bandit](https://github.com/PyCQA/bandit) for issues\nsuch as not sanitising user inputs or using weak cryptography.\n\n**Warning:** Bandit is a static analysis tool and can\'t check for issues that are only be detectable when running the\napplication. As with all security tools, Bandit is an aid for spotting common mistakes, not a guarantee of secure code.\n\nTo check manually from the command line:\n\n```shell\n$ docker-compose run app bandit -r .\n```\n\nChecks are ran automatically in [Continuous Integration](#continuous-integration).\n\n## Testing\n\n### Integration tests\n\nThis project uses integration tests to ensure features work as expected and to guard against regressions and\nvulnerabilities.\n\nThe Python [UnitTest](https://docs.python.org/3/library/unittest.html) library is used for running tests using Flask\'s\ntest framework. Test cases are defined in files within `tests/` and are automatically loaded when using the `test`\nFlask CLI command included in the local Flask application in the development environment.\n\nTo run tests manually:\n\n```shell\n$ docker-compose run -e FLASK_ENV=testing app flask test --test-runner text\n```\n\nTo run tests manually using PyCharm, use the included *App (Tests)* run/debug configuration.\n\nTests are ran automatically in [Continuous Integration](#continuous-integration).\n\n### Continuous Integration\n\nAll commits will trigger a Continuous Integration process using GitLab\'s CI/CD platform, configured in `.gitlab-ci.yml`.\n\n### Test/Example applications\n\nFor verifying this provider works for real-world use-cases, a test Flask application is included in \n`examples/__init__.py`. This test application acts as both an application providing access to, and accessing, protected\nresources. It can use a number of application registrations registered in the BAS Web & Applications Test Azure AD.\n\nThese applications allow testing different versions of access tokens for example. These applications are intended for \ntesting only. They do not represent real applications, or contain any sensitive or protected information. \n\nTo test requesting resources from protected resources as an API, set the appropriate config options and run the \napplication container:\n\n```shell\n$ docker-compose run app\n$ flask access-resource [resource]\n```\n\nTo test requesting resources from protected resources as a browser application, set the appropriate config options and \nstart the application container:\n\n```shell\n$ docker-compose up\n```\n\nTerraform is used to provision the application registrations used:\n\n```\n$ cd provisioning/terraform\n$ docker-compose run terraform\n$ az login --allow-no-subscriptions\n$ terraform init\n$ terraform validate\n$ terraform apply\n```\n\n**Note:** Several properties in the application registration resources require setting once the registration has been \ninitially made (identifiers for example). These will need commenting out before use.\n\nSome properties, such as client secrets, can only be set once applications have been registered in the Azure Portal.\n\nTerraform state information is held in the BAS Terraform Remote State project (internal).\n\n## Deployment\n\n### Python package\n\nThis project is distributed as a Python package, hosted in [PyPi](https://pypi.org/project/flask-azure-oauth).\n\nSource and binary packages are built and published automatically using\n[Poetry](https://python-poetry.org/docs/cli/#publish) in [Continuous Delivery](#continuous-deployment).\n\nPackage versions are determined automatically using the `support/python-packaging/parse_version.py` script.\n\n### Continuous Deployment\n\nA Continuous Deployment process using GitLab\'s CI/CD platform is configured in `.gitlab-ci.yml`.\n\n## Release procedure\n\nFor all releases:\n\n1. create a `release` branch\n2. close release in `CHANGELOG.md`\n3. push changes, merge the `release` branch into `master` and tag with version\n\nThe project will be built and published to PyPi automatically through [Continuous Deployment](#continuous-deployment).\n\n## Feedback\n\nThe maintainer of this project is the BAS Web & Applications Team, they can be contacted at:\n[servicedesk@bas.ac.uk](mailto:servicedesk@bas.ac.uk).\n\n## Issue tracking\n\nThis project uses issue tracking, see the\n[Issue tracker](https://gitlab.data.bas.ac.uk/web-apps/flask-extensions/flask-azure-oauth/issues) for more information.\n\n**Note:** Read & write access to this issue tracker is restricted. Contact the project maintainer to request access.\n\n## License\n\n© UK Research and Innovation (UKRI), 2019 - 2020, British Antarctic Survey.\n\nYou may use and re-use this software and associated documentation files free of charge in any format or medium, under\nthe terms of the Open Government Licence v3.0.\n\nYou may obtain a copy of the Open Government Licence at http://www.nationalarchives.gov.uk/doc/open-government-licence/\n',
    'author': 'Felix Fennell',
    'author_email': 'felnne@bas.ac.uk',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/antarctica/flask-azure-oauth',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.6,<4.0',
}


setup(**setup_kwargs)
