Skip to content

Commit

Permalink
Update authorization docs
Browse files Browse the repository at this point in the history
  • Loading branch information
paul121 committed Jan 8, 2025
1 parent 6f83392 commit c572390
Showing 1 changed file with 36 additions and 88 deletions.
124 changes: 36 additions & 88 deletions docs/authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
```

0 comments on commit c572390

Please sign in to comment.