diff --git a/docs/authorization.md b/docs/authorization.md index 57ffe53..b47ec0d 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -2,104 +2,52 @@ ## Background -The farmOS.py client authenticates with the farmOS server via OAuth `Bearer` -tokens. Before authenticating with the server, a farmOS client must be -created and an OAuth Authorization flow must be completed (unless an optional -`token` was provided when creating the client). +farmOS includes an OAuth2 Authorization server for providing 1st and 3rd party +clients access to the farmOS API. For more information on the OAuth2 +specification see the [farmOS API authentication documentation](https://farmos.org/development/api/authentication/). -## Authorizing with Password Credentials (most common) +farmOS.py provides a `FarmClient` wrapper around the [HTTPX](https://www.python-httpx.org/) +HTTP Python client and uses the [HTTPX-Auth](https://github.com/Colin-b/httpx_auth) +library for OAuth2 authentication. For advanced use-cases that require other +authentication schemes see [HTTPX custom authentication schemes](https://www.python-httpx.org/advanced/authentication/#custom-authentication-schemes). -```python -from farmOS import farmOS - -hostname = "myfarm.farmos.net" -username = "username" -password = "password" - -# Create the client. -farm_client = farmOS( - hostname=hostname, - client_id = "farm", # Optional. The default oauth client_id "farm" is enabled on all farmOS servers. - scope="farm_manager", # Optional. The default scope is "farm_manager". Only needed if authorizing with a different scope. - version=2 # Optional. The major version of the farmOS server, 1 or 2. Defaults to 2. -) - -# Authorize the client, save the token. -# A scope can be specified, but will default to the default scope set when initializing the client. -token = farm_client.authorize(username, password, scope="farm_manager") -``` - -Running from a Python Console, the `username` and `password` can also be -omitted and entered at runtime. This allows testing without saving -credentials in plaintext: - -```python ->>> from farmOS import farmOS ->>> farm_client = farmOS(hostname="myfarm.farmos.net") ->>> farm_client.authorize() -Warning: Password input may be echoed. -Enter username: >? username -Warning: Password input may be echoed. -Enter password: >? password ->>> farm_client.info() -``` +## OAuth2 Authorization Flow -## Authorizing with existing OAuth Token (advanced) +Before making requests to the farmOS server an OAuth2 client and grant must be +configured on the farmOS server to be used in an OAuth2 authorization flow. -An existing token can be provided when creating the farmOS client. This is -useful for advanced use cases where an OAuth token may be persisted. +An OAuth Client represents a 1st or 3rd party integration with the farmOS +server. Clients are uniquely identified by a `client_id` and can have an +optional `client_secret` for private integrations. Clients are configured +to allow only specific OAuth grants and can specify default scopes that +are granted when none are requested. -```python -from farmOS import farmOS - -hostname = "myfarm.farmos.net" -token = { - "access_token": "abcd", - "refresh_token": "abcd", - "expires_at": "timestamp", -} - -# Create the client with existing token. -farm_client = farmOS( - hostname=hostname, - token=token, -) -``` +The OAuth2 Password Credentials Flow is documented here because most Python +scripting use-cases can be trusted with a username and password (considered +a 1st party client). The core `farm_api_default_consumer` module provides a +default client with `client_id = farm` that can use the `password` grant. You +can use this client for general usage of the API, like writing a script that +communicates with your farmOS server, but it comes with limitations. For more +information on OAuth2 authorization flows supported by the farmOS server see the +[farmOS Authorization Flow documentation](https://farmos.org/development/api/authentication/#authorization-flows). -## Saving OAuth Tokens +## Usage in farmOS.py -By default, access tokens expire in 1 hour. This means that requests sent 1 -hour after authorization will trigger a `refresh` flow, providing the client -with a new `access_token` to use. A `token_updater` can be provided to save -tokens external of the session when automatic refreshing occurs. - -The `token_updater` defaults to an empty lambda function: `lambda new_token: None`. -Alternatively, set `token_updater = None` to allow the [`requests_oauthlib.TokenUpdated`](https://requests-oauthlib.readthedocs.io/en/latest/api.html#requests_oauthlib.TokenUpdated) -exception to be raised and caught by code executing requests from farmOS.py. +Instantiate an OAuth2 flow from the HTTPX-Auth library. Pass this to the +`FarmClient` using the `auth` parameter: ```python -from farmOS import farmOS - -hostname = "myfarm.farmos.net" -username = "username" -password = "password" +from httpx_auth import OAuth2ResourceOwnerPasswordCredentials +from farmOS import FarmClient -# Maintain an external state of the token. -current_token = None +FARMOS_HOSTNAME="https://myfarm.farmos.net" -# Callback function to save new tokens. -def token_updater(new_token): - print(f"Got a new token! {new_token}") - # Update state. - current_token = new_token - -# Create the client. -farm_client = farmOS( - hostname=hostname, - token_updater=token_updater, # Provide the token updater callback. +auth = OAuth2ResourceOwnerPasswordCredentials( + token_url=f"{FARMOS_HOSTNAME}/oauth/token", + username=USERNAME, + password=PASSWORD, + client_id="farm", + scope="farm_manager", ) - -# Authorize the client. -# Save the initial token that is created. -current_token = farm_client.authorize(username, password, scope="farm_manager") +farm_client = FarmClient(hostname=FARMOS_HOSTNAME, auth=auth) ``` \ No newline at end of file