API¶
farmOS has a REST API that other applications/systems can use to communicate with farmOS programatically. The API provides CRUD operations for all of the main farmOS record types (Drupal entity types). This is accomplished with the RESTful Web Services module.
Note for Sensor Data: If you are looking for documentation about pushing and pulling data from sensors in farmOS, see Sensors.
The following documentation provides a brief overview of the farmOS API for developers who are interested in using it. For more general information, please refer to the RESTful Web Services module documentation.
In all of the commands below, replace the following placeholders with your specific farmOS URL, username, and password.
[URL]
- Base URL of the farmOS instance, without trailing slash (eg:https://example.farmos.net
)[USER]
- User name (eg:MyUserName
)[PASS]
- Password (eg:MyPassword
)[AUTH]
- Authentication parameters forcurl
commands (this will depend on the authentication method you choose below).
Authentication¶
There are three ways to authenticate with a farmOS server:
- OAuth2 Authorization Tokens (recommended)
- Session Cookie and CSRF Token
- Basic Authentication
1. OAuth2 Authorization Tokens¶
farmOS includes an OAuth2 Authorization server for providing 3rd party clients access to the farmOS API. Rather than using a user's username and password to authenticate, OAuth2 uses access tokens to authenticate users. The access tokens are provided to both 1st and 3rd party clients who wish to access a user's protected resources from a server. Clients store the access token instead of the user's credentials, which makes it a more secure authentication method.
Read more about the OAuth 2.0 standards
For more information about authenticating with OAuth2, see the OAuth2 Details section below.
Once you have an OAuth2 token, you can pass it to farmOS with an
Authorization: Bearer
header.
-H "Authorization: Bearer [OAUTH-TOKEN]"
This should be used to replace [AUTH]
in the curl
examples that follow.
2. Session Cookie and CSRF Token¶
The old approach (before OAuth2 was introduced in farmOS 7.x-1.4), was to
authenticate via Drupal's user_login
form and save the session cookie provided
by Drupal. Then, you can use that to retrieve a CSRF token from
[URL]/restws/session/token
, which is provided and required by the restws
module. The cookie and token can then be included with each API request.
The following curl
command will authenticate using the username and password,
and save the session cookie to a file called farmOS-cookie.txt
. Then it will
get a session token and save that to a $TOKEN
variable. Together, the cookie
and token can be used to make authenticated requests.
curl --cookie-jar farmOS-cookie.txt -d 'name=[USER]&pass=[PASS]&form_id=user_login' [URL]/user/login
TOKEN="$(curl --cookie farmOS-cookie.txt [URL]/restws/session/token)"
After running those two commands, the cookie and token can be included with
subsequent curl
requests via the --cookie
and -H
parameters:
--cookie farmOS-cookie.txt -H "X-CSRF-Token: ${TOKEN}"
This should be used to replace [AUTH]
in the curl
examples that follow.
3. Basic Authentication¶
An alternative approach is to use HTTP Basic Authentication.
The RESTful Web Services module comes with a Basic Authentication sub-module, which provides the ability to include the username and password with each request using HTTP Basic Authentication. This modules is included with farmOS but is disabled by default.
By default the module only tries to authenticate usernames prefixed with 'restws'. This can be changed by modifying the regex used against usernames - see the RESTful Web Services Basic Authentication module documentation.
SSL encryption is highly recommended. This option requires sending credentials with every request which means extra care must be taken to not expose these credentials. Using SSL will encrypt the credentials with each request. Otherwise, they will be sent in plaintext.
Using basic authentication makes it much easier to test the API with development tools such as Postman or Restlet. You just need to add basic authentication credentials to the request header. The request returns a cookie, which seems to be saved by these applications. This means subsequent requests don't need the authentication header, as long as the cookie is saved.
Basic Authentication can be used in curl
requests via the -u
parameter:
-u [USER]:[PASS]
This should be used to replace [AUTH]
in the curl
examples that follow.
/farm.json¶
farmOS provides an informational API endpoint at /farm.json
, which includes
the farm name, URL, API version, the system of measurement used by the farm,
information about the currently authenticated user, installed languages, and
information about available entity types and bundles.
For example:
{
"name": "My Farm",
"url": "https://myfarm.mydomain.com",
"api_version": "1.0",
"system_of_measurement": "metric",
"user": {
"uid": "3",
"name": "My Username",
"mail": "myemail@mydomain.com",
"language": "en",
},
"languages": {
"en": {
"language": "en",
"name": "English",
"native": "English",
"direction": 0,
},
},
"resources": {
"log": {
"farm_activity": {
"label": "Activity",
"label_plural": "Activities",
"fields" {
"area": {
"label": "Areas",
"type": "taxonomy_term_reference",
"required": 0,
},
...
},
}
},
...
}
}
The system_of_measurement
with be either metric
or us
.
The resources
section contains a list of entity types (aka "resource types")
that are available. Within each is a list of bundles (eg: "log types", "asset
types", etc). Each bundle contains information such as its translated label
,
and a list of available fields
and their metadata (label
, required
, etc).
The taxonomy_term
bundles also contain their vid
(vocabulary ID), which is
necessary when creating new terms (see Creating taxonomy terms).
Other modules can add information to /farm.json
with hook_farm_info()
.
API Version¶
Current API version: 1.3
It is highly recommended that you check the API version of the farmOS system you are communicating with, to be sure that your code is using the same version. If any changes are made that are not backwards compatible, the API version will change. So by checking it in your code, you can prevent unexpected things from happening if you try to use unsupported API features.
Requesting records¶
The following curl
command examples demonstrate how to get lists of records
and individual records in JSON format. They use the stored farmOS-cookie.txt
file and $TOKEN
variable generated with the commands above.
Lists of records
This will retrieve a list of harvest logs in JSON format:
curl [AUTH] [URL]/log.json?type=farm_harvest
Records can also be requested in XML format by changing the file extension in the URL:
curl [AUTH] [URL]/log.xml?type=farm_harvest
Individual records
Individual records can be retrieved in a similar way, by including their entity ID. This will retrieve a log with ID 1:
curl [AUTH] [URL]/log/1.json
Endpoints
The endpoint to use depends on the entity type you are requesting:
- Assets:
/farm_asset.json
- Plantings:
/farm_asset.json?type=planting
- Animals:
/farm_asset.json?type=animal
- Equipment:
/farm_asset.json?type=equipment
- ...
- Plantings:
- Logs:
/log.json
- Activities:
/log.json?type=farm_activity
- Observations:
/log.json?type=farm_observation
- Harvests:
/log.json?type=farm_harvest
- Inputs:
/log.json?type=farm_input
- ...
- Activities:
- Taxonomy terms:
/taxonomy_term.json
- Areas*:
/taxonomy_term.json?bundle=farm_areas
- Quantity Units:
/taxonomy_term.json?bundle=farm_quantity_units
- Crops/varieties:
/taxonomy_term.json?bundle=farm_crops
- Animal species/breeds:
/taxonomy_term.json?bundle=farm_animal_types
- Log categories:
/taxonomy_term.json?bundle=farm_log_categories
- Seasons:
/taxonomy_term.json?bundle=farm_season
- ...
- Areas*:
* Note that areas are currently represented as Drupal taxonomy terms, but may be changed to assets in the future. See Make "Area" into a type of Farm Asset.
Filtering
Filters can be applied via query parameters tacked on to the end of the URL. For example, to show only "done" logs:
/log.json?done=1
Refer to lists of fields under "Creating records" below for available filters.
When a record references other records (for example, a log that references an asset), you must filter by the referenced record's ID. For example, to find logs that reference asset ID 5:
/log.json?asset=5
Taxonomy term references are an exception to this rule. It is possible to filter using the name of the term, rather than the ID. For example, to show logs with a category of "Tillage":
/log.json?log_category=Tillage
The term name is also included in the returned JSON structure, alongside the term ID, so that extra requests to look up term names are not necessary.
Creating records¶
Records can be created with a POST
request of JSON/XML objects to the endpoint
for the entity type you would like to create.
Creating logs¶
First, here is a very simple example of an observation log in JSON. The bare
minimum required fields are name
, type
, and timestamp
.
{
"name": "Test observation via REST",
"type": "farm_observation",
"timestamp": "1526584271",
}
Here is a curl
command to create that log in farmOS:
curl -X POST [AUTH] -H 'Content-Type: application/json' -d '{"name": "Test observation via REST", "type": "farm_observation", "timestamp": "1526584271"}' [URL]/log
Most log types have the same set of standard fields. Some specific log types differ slightly. The following are standard fields available on most log types:
id
: The log ID (unique database primary key).name
: The log name.type
: The log type. Some types that are included in farmOS are:farm_activity
: General activity logs.farm_observation
: General observation logs.farm_input
: An input to one or more assets/areas.farm_harvest
: A harvest from one or more assets/areas.farm_seeding
: A seeding log (specific to Planting assets).farm_transplanting
: A transplanting log (specific to Planting assets).farm_maintenance
: A maintenance log (specific to Equipment assets).
timestamp
: A timestamp representing when the logged event took place.done
: Boolean value indicating whether the log is "done" or not.notes
: A free-form text field for log notes.- Note that this should be a JSON object with a
value
property and aformat
property set tofarm_format
.
- Note that this should be a JSON object with a
asset
: Reference(s) to asset(s) that the log is associated with.equipment
: Reference(s) to equipment assets that were used in the process.area
: Reference(s) to area(s) that the log is associated with.geofield
: Geometry specific to the logged event.movement
: Movement information (see "Field collections" below).membership
: Group membership information (see "Field collections" below).quantity
: Quantity measurements (see "Field collections" below).images
: Image files attached to the log.files
: Other files attached to the log.flags
: Flags associated with the log. Examples:priority
monitor
review
log_category
: A reference to one or more log categories.log_owner
: A reference to one or more users that the log is assigned to.- Note that this is different from
uid
, which is the user who created the log.
- Note that this is different from
created
: A timestamp representing when the log was created.changed
: A timestamp representing when the log was last updated.uid
: A reference to the user who created the log.data
: An arbitrary string of data. This can be used to store additional data on the log as JSON, XML, YAML, etc. It is the resposibility of the API user to respect data format that is already in the log.
Input logs¶
Additional fields on input logs (all optional):
material
: Reference(s) to material(s) that were applied.input_purpose
: A description of the purpose of the input.input_method
: A description of how the input was applied.input_source
: A description of where the input was obtained.date_purchase
: A timestamp representing when the input material was purchased.lot_number
: The lot number(s) of the input material, if available.
Harvest logs¶
Additional fields on harvest logs (all optional):
lot_number
: The harvest lot number.
Seeding and transplanting logs¶
Seeding and transplanting logs are log types that are specific to Planting assets (to indicate when seedings/transplantings took place, along with quantity and location information). They have some key differences from other log types:
- The
asset
field is required and must reference an asset of typeplanting
. - They do not have the
areas
orgeofield
fields, and instead expect to have amovement
field collection with information about where it took place. This is used to set the location of the planting at a given point in time. - Seeding logs also have a
seed_source
field which can be used to specify where seeds came from (free-form text field).
Creating assets¶
Assets must have a name
and a type
, and some asset types have additional
required fields. For example planting
assets require a crop
reference (for
crop/variety), animal
assets require an animal_type
reference (for
species/breed), and other types may have other requirements. A quick way to find
out which fields are required is to open the "Add asset" form in farmOS and look
for the red asterisk that indicates a required field.
A very basic "Equipment" asset looks like this:
{
"name": "Tractor",
"type": "equipment",
"manufacturer": "Allis-Chambers",
"model": "G",
"serial_number": "1234567890",
}
Here is a curl
command to create that asset in farmOS:
curl -X POST [AUTH] -H 'Content-Type: application/json' -d '{"name": "Tractor", "type": "equipment", "manufacturer": "Allis-Chambers", "model": "G", "serial_number": "1234567890"}' [URL]/farm_asset
Most asset types have the same set of standard fields. Some specific asset types differ slightly. The following are standard fields available on most log types:
id
: The asset ID (unique database primary key).name
: The asset name.type
: The asset type. Some types that are included in farmOS are:planting
animal
equipment
group
description
: A free-form text field for an asset description.- Note that this should be a JSON object with a
value
property and aformat
property set tofarm_format
.
- Note that this should be a JSON object with a
archived
: A timestamp representing when the asset was archived. If this is0
then the asset is not archived.images
: Image files attached to the asset.files
: Other files attached to the asset.flags
: Flags associated with the log. Examples:priority
monitor
review
created
: A timestamp representing when the asset was created.changed
: A timestamp representing when the asset was last changed.uid
: A reference to the user who created the asset.data
: An arbitrary string of data. This can be used to store additional data on the asset as JSON, XML, YAML, etc. It is the resposibility of the API user to respect data format that is already in the log.
Assets will also have the following special-purpose fields, which are computed based on the asset's log history. These fields cannot be written to directly, they can only be changed via logs.
location
: An array of references to areas that the asset is currently located in, based on its most recent completed movement log.geometry
: The current geometry of the asset, based on its most recent completed movement log.
Planting assets¶
Additional fields on animal assets:
crop
: Reference(s) to the crops/varieties this is a planting of. Required.parent
: Reference(s) to assets that this planting is derived from.
Animal assets¶
Additional fields on animal assets:
animal_type
: Reference to the animal's species/breed. Required.animal_nicknames
: Nickname(s) for the animal.animal_castrated
: Boolean indicating whether or not this animal has been castrated.animal_sex
: The sex of the animal. Available options:F
(female)M
(male)
tag
: ID Tag(s) that are assigned to the animal (see "Field collections" below).parent
: Reference(s) to the animal's parent(s).date
: A timestamp representing the birth date of the animal.
Equipment assets¶
Additional fields on equipment assets (all optional):
manufacturer
: The equipment manufacturer.model
: The equipment model.serial_number
: The equipment serial number.
Creating taxonomy terms¶
farmOS allows farmers to build vocabularies of terms for various categorization purposes. These are referred to as "taxonomies" in farmOS (and Drupal), although "vocabulary" is sometimes used interchangeably.
Some things that are represented as taxonomy terms include quantity units, crops/varieties, animal species/breeds, input materials, and log categories. See "Endpoints" above for specific API endpoints URLs.
A very basic taxonomy term JSON structure looks like this:
{
"tid": "3",
"name": "Cabbage",
"description": "",
"vocabulary": {
"id": "7",
"resource": "taxonomy_vocabulary",
},
"parent": [
{
"id": "10",
"resource": "taxonomy_term",
},
],
"weight": "5",
}
The tid
is the unique ID of the term (database primary key). When creating a
new term, the only required fields are name
and vocabulary
. The vocabulary
is an ID that corresponds to the specific vocabulary the term will be a part of
(eg: quantity units, crops/varieties, log categories, etc). The fields parent
and weight
control term hierarchy and ordering (a heavier weight
will sort
it lower in the list).
When fetching a list of terms from farmOS, you can filter by the vocabulary's "machine name" (or "bundle") like so:
curl [AUTH] [URL]/taxonomy_term.json?bundle=farm_crops
When creating a new term, however, you need to provide the vocabulary ID in the
term object, not the machine name/bundle. In order to get the vocabulary ID, you
can look it up using the /taxonomy_vocabulary
endpoint, which lists all
available vocabularies. Or, you can find it in farm.json
(described above).
The vocabulary ID may be different from one farmOS system to another, so if you save it internally in your application, just know that it may not correspond to the same vocabulary on another farmOS. Therefore it is recommended that you look it up each time you need it.
For example, to get the vid
that corresponds to the farm_crops
vocabulary:
curl [AUTH] [URL]/taxonomy_vocabulary.json?machine_name=farm_crops
Or, by loading the information in farm.json
and parsing out the vocabulary ID:
curl [AUTH] [URL]/farm.json
Before you create a new term, you should check to see if it already exists:
curl [AUTH] [URL]/taxonomy_term.json?bundle=farm_crops&name=Broccoli
Then, you can create a new term (replace [VID]
with the ID of the vocabulary
the term should be added to):
curl [AUTH] -X POST -H 'Content-Type: application/json' -d '{"name": "Broccoli", "vocabulary": "[VID]"}' [URL]/taxonomy_term
For convenience, it is possible to create terms on-the-fly when creating or
updating other records that reference them. Simply provide the name
of the
term instead of the tid
, and farmOS will attempt to look up the existing term,
or create a new one.
For example, to create a "2020 Spinach" planting asset, a "Spinach" crop term, and a "2020" season term all at the same time:
{
"name": "2020 Spinach",
"type": "planting",
"crop": [{"name": "Spinach"}],
"season": [{"name": "2020"}],
}
Creating an area
Areas are taxonomy terms in farmOS, but they have some additional fields on them
for mapping purposes. In order for an area to show up on a map, it must have an
area_type
set, and a geometry in the geofield
.
A very basic area record looks like this:
{
"name": "North field",
"description": "",
"area_type": "field",
"geofield": [
{
"geom": "POINT(-31.04003861546518 39.592143995003994)",
},
],
}
The geometry should be in Well-Known Text.
Some available area types include:
field
bed
building
greenhouse
property
paddock
water
landmark
other
Note that area types are defined by modules (eg: paddock
is provided by the
farm_livestock
module), so the area types available on your farmOS may vary.
You can fetch all areas of a particular type (eg: field
) like this:
curl [AUTH] [URL]/taxonomy_term.json?bundle=farm_areas&area_type=field
Updating records¶
Records can be updated with a PUT
request of JSON/XML objects to the record's
endpoint (with ID). Only include the fields you want to update - everything
else will be left unchanged.
Do not add .json
or .xml
to the endpoint's URL. Instead, add a
Content-Type
header that specifies either application/json
or
application/xml
.
For example, to change the name of log 1:
curl -X PUT [AUTH] -H 'Content-Type: application/json' -d '{"name": "Change the log name"}' [URL]/log/1
Areas will also have the following special-purpose fields, which are computed based on log history. These fields cannot be written to directly, they can only be changed via logs.
assets
: An array of references to assets that are currently located in the area, based on asset movement logs.
Field collections¶
Some fields are stored within Field Collections in farmOS, which are technically separate entities attached to the host entity. farmOS uses the RESTful Web Services Field Collection module to hide the fact that these are separate entities, so that their fields can be accessed and modified in the same request as the host entity.
In farmOS Field Collections are used for the following:
Quantity measurements¶
Quantity measurements are recorded on logs, and can each include a measure (eg: count, weight, volume, etc), a value (decimal number), a unit (a reference to a Units taxonomy term, and a label (a free-form text field).
A log with a quantity measurement looks like this:
{
"name": "Example quantity measurement log",
"type": "farm_observation",
"timestamp": "1551983893",
"quantity": [
{
"measure": "weight",
"value": "100",
"unit": {"id": "15"},
"label": "My label",
},
],
}
Available measure
options are:
count
length
weight
area
volume
time
temperature
value
rate
rating
ratio
probability
All measurement properties are optional. The label is useful if you have more than one measurement of the same measure (eg: two weight measurements) in the same log. Use the label to add more specific information about what each measurement is for.
Movements¶
Movements are defined on logs, and include an area reference (where the asset(s) are moving to), and optionally a geometry (if you need to define a more specific location than the area reference itself).
A log with a movement looks like this:
{
"name": "Example movement log",
"type": "farm_activity",
"timestamp": "1551983893",
"asset": [
{"id": "123"},
],
"movement": {
"area": [
{"id": "5"},
],
"geometry": "POINT(-31.04003861546518 39.592143995003994)",
},
}
Within the movement
structure, the area ID should be an area term ID (tid
on
the area object). The geometry should be in Well-Known Text. It is possible to
reference multiple areas, indicating that the asset is moved to multiple areas
(eg: a planting in multiple beds).
Group membership¶
Group membership changes are defined on logs, and include one or more group asset IDs. This indicates that any referenced asset(s) were moved to the group(s) specified at the time of the log.
A log with a group membership change looks like this:
{
"name": "Example group membership change log",
"type": "farm_activity",
"timestamp": "1551983893",
"asset": [
{"id": "123"},
],
"membership": {
"group": [
{"id": "456"},
],
},
}
Groups are a type of asset in farmOS, so the group ID is actually an asset ID.
Inventory adjustments¶
Inventory adjustments are defined on logs, and include one or more pairs of asset IDs (the asset for which inventory is being adjusted) and an adjustment value (postitive or negative number, to indicate an increase or decrease in the inventory level).
A log with an inventory adjustment looks like this:
{
"name": "Example group membership change log",
"type": "farm_activity",
"timestamp": "1551983893",
"inventory": [
{
"asset": {"id": "123"},
"value": "-10",
},
],
}
Animal ID Tags¶
Animal ID Tags can be added to animal assets, and include a Tag ID, Tag Type, and Tag Location.
An animal asset with an ID tag looks like this:
{
"name": "Betsy",
"type": "animal",
"animal_type": {"id": "10"},
"tag": [
{
"id": "123456",
"location": "Left ear",
"type": "Ear tag",
},
],
}
The ID and Location fields can be anything you want.
Available tag types are:
Brand
Ear tag
Tattoo
Leg band
Chip
Other
Uploading files¶
Files can be attached to records using the API.
Most record types (areas, assets, logs, etc) have both a "Photo(s)" field (for image files) and a "File(s)" field (for other files). The machine names of these fields are:
- Photo(s):
images
- File(s):
files
The API expects an array of base64-encoded strings. Multiple files can be included in the array.
For example (replace [BASE64_CONTENT]
with the base64 encoding of the file):
{
"name": "Test file upload via REST",
"type": "farm_observation",
"timestamp": "1534261642",
"images": [
"data:image/jpeg;base64,[BASE64_CONTENT]",
],
}
Here is an example using curl
(replace [filename]
with the path to the
file). This will create a new observation log with the file attached to the
"Photo(s)" field of the log.
# Get the file MIME type.
FILE_MIME=`file -b --mime-type [filename]`
# Encode the file as a base64 string and save it to a variable.
FILE_CONTENT=`base64 [filename]`
# Post a log with the file attached to the photo field.
# <<CURL_DATA is used because base64 encoded files are generally longer
# the max curl argument length. See:
# https://unix.stackexchange.com/questions/174350/curl-argument-list-too-long
curl -X POST [AUTH] -H "Content-Type: application/json" -d @- [URL]/log <<CURL_DATA
{"name": "Test file upload via REST", "type": "farm_observation", "timestamp": "1534261642", "images": ["data:${FILE_MIME};base64,${FILE_CONTENT}"]}
CURL_DATA
OAuth2 Details¶
The following describes more of the details necessary for using OAuth2 for authentication with a farmOS server.
Scopes¶
OAuth Scopes define different levels of permission. Two scopes are included with farmOS:
user_access
: This allows full user access to the farmOS server. With this scope, a third party can do anything the farmOS user account has permission to do.farm_info
: Allows access to data at/farm.json
Clients¶
In order to connect to farmOS via OAuth2, there must be a "client" configured on the server that corresponds to the client that will be making the requests. This is necessary so that tokens can be revoked from specific clients.
The core farm_api
module provides a default client called farm
, which
supports the Password Credentials and Refresh Token grants (more info in
"Authorization Flows" below). If you are writing a custom script that
communicates with farmOS via the API, this is the recommended approach for
authenticating with username and password.
The core farm_api
module also comes with a farm_api_development
module that
can be enabled for testing different OAuth authorization flows. For the purposes
of documentation, this client is used in below examples. This module defines
an OAuth client with the following parameters:
client_id
=farmos_development
client_secret
= None. This client does not require aclient_secret
redirect_uri
=http://localhost/api/authorized
(This is required for the Authorization Code Grant)
A client can be added via a farmOS module by implementing
hook_farm_api_oauth2_client()
. The following example defines a client for a
fictional farmOS Aggregator called "My Aggregator":
<?php
/**
* Implements hook_farm_api_oauth2_client().
*/
function myaggregator_farm_api_oauth2_client() {
$clients = array();
// Define an OAuth2 client for My Aggregator.
$redirect_uris = array(
'https://myfarmosaggregator.com/register-farm',
'https://myfarmosaggregator.com/authorize-farm',
);
$clients['my_aggregator'] = array(
'label' => 'My Aggregator',
'client_key' => 'my_farmos_client',
'redirect_uri' => implode("\n", $aggregator_redirect_uris),
);
return $clients;
}
Authorization Flows¶
The OAuth 2.0 standards outline 5 Oauth2 Grant Types to be used in an OAuth2 Authorization Flow - They are the Authorization Code, Implicit, Password Credentials, Client Credentials and Refresh Token Grants. Currently, the farmOS OAuth Server only supports the Authorization Code, Password Credentials and Refresh Token grants. The Authorization Code and Refresh Token grants are the only Authorization Flows recommended by farmOS for use with 3rd party clients.
NOTE: Only use the Password Grant if the client can be trusted with a farmOS username and password (this is considered 1st party). The Client Credentials Grant is often used for machine authentication not associated with a user account. Due to limitations with the Drupal 7 oauth2_server module, access tokens provided via the Client Credentials Grant cannot be associated with the Drupal Permissions system to access protected resources. farmOS will hopefully support the Client Credentials Grant when farmOS migrates to Drupal 8 and can use the simple_oauth module.
NOTE: The oauth2/token
and oauth2/authorize
endpoints are deprecated and
will be removed in farmOS 2.x. Starting with farmOS 1.6 it is recommended to use
the new oauth/token
and oauth/authorize
endpoints.
Authorization Code Grant¶
The Authorization Code Grant is most popular for 3rd party client authorization.
Requesting resources is a four step process:
First: the client sends a request to the farmOS server /oauth/authorize
endpoint requesting an Authorization Code
. The user logs in and authorizes
the client to have the OAuth Scopes it is requesting.
Copy this link to browser -
http://localhost/oauth/authorize?response_type=code&client_id=farmos_development&scope=user_access&redirect_uri=http://localhost/api/authorized&state=p4W8P5f7gJCIDbC1Mv78zHhlpJOidy
Second: after the user accepts, the server redirects
to the redirect_uri
with an authorization code
and state
in the query
parameters.
Example redirect url from server:
http://localhost/api/authorized?code=9eb9442c7a2b011fd59617635cca5421cd089943&state=p4W8P5f7gJCIDbC1Mv78zHhlpJOidy
Third: copy the code
and state
from the URL into the body of a POST request.
The grant_type
, client_id
, client_secret
and redirect_uri
must also be
included in the POST body. The client makes a POST request to the
/oauth/token
endpoint to retrieve an access_token
and refresh_token
.
foo@bar:~$ curl -X POST -d "grant_type=authorization_code&code=ae4d1381cc67def1c10dc88a19af6ac30d7b5959&client_id=farmos_development&redirect_uri=http://localhost/api/authorized" http://localhost/oauth/token
{"access_token":"3f9212c4a6656f1cd1304e47307927a7c224abb0","expires_in":"10","token_type":"Bearer","scope":"user_access","refresh_token":"292810b04d688bfb5c3cee28e45637ec8ef1dd9e"}
Fourth: the client sends the access token in the request header to access protected
resources. The header is an Authorization header with a Bearer token:
Authorization: Bearer access_token
foo@bar:~$ curl --header "Authorization: Bearer b872daf5827a75495c8194c6bfa4f90cf46c143e" http://localhost/farm.json
{"name":"farmos-server","url":"http:\/\/localhost","api_version":"1.1","user":{"uid":"1","name":"admin", ....
Password Credentials Grant¶
NOTE: Only use the Password Grant if the client can be trusted with a farmOS username and password (this is considered 1st party).
The Password Credentials Grant uses a farmOS username
and password
to
retrieve an access_token
and refresh_token
in one step. For the user, this
is the simplest type of authorization. Because the client can be trusted with
their farmOS Credentials, a users username
and password
can be collected
directly into a login form within the client application. These credentials are
then used (not stored) to request tokens which are used for authentication
with the farmOS server and retrieving data.
Requesting protected resources is a two step process:
First, the client sends a POST request to the farmOS server /oauth/token
endpoint with grant_type
set to password
and a username
and password
included in the request body.
$ curl -X POST -d "grant_type=password&username=username&password=test&client_id=farm&scope=user_access" http://localhost/oauth/token
{"access_token":"e69c60dea3f5c59c95863928fa6fb860d3506fe9","expires_in":"300","token_type":"Bearer","scope":"user_access","refresh_token":"cead7d46d18d74daea83f114bc0b512ec4cc31c3"}
second, the client sends the access_token
in the request header to access protected
resources. The header is an Authorization header with a Bearer token:
Authorization: Bearer access_token
foo@bar:~$ curl --header "Authorization: Bearer e69c60dea3f5c59c95863928fa6fb860d3506fe9" http://localhost/farm.json
{"name":"farmos-server","url":"http:\/\/localhost","api_version":"1.1","user":{"uid":"1","name":"admin", ....
Refreshing Tokens¶
The refresh_token
can be used to retrieve a new access_token
if the token
has expired.
It is a one step process:
The client sends an authenticated request to the /oauth/token
endpoint with
grant_type
set to refresh_token
and includes the refresh_token
,
client_id
and client_secret
in the request body.
foo@bar:~$ curl -X POST -H 'Authorization: Bearer ad52c04d26c1002084501d28b59196996f0bd93f' -d 'refresh_token=52e7a0e12e8ddd08b155b3b3ee385687fef01664&grant_type=refresh_token&client_id=farmos_api_client&client_secret=client_secret' http://localhost/oauth/token
{"access_token":"acdbfabb736e42aa301b50fdda95d6b7fd3e7e14","expires_in":"300","token_type":"Bearer","scope":"user_access","refresh_token":"b73f4744840498a26f43447d8cf755238bfd391a"}
The server responds with an access_token
and refresh_token
that can be used
in future requests. The previous access_token
and refresh_token
will no
longer work.
OAuth2 Authorization with the farmOS API¶
Authorization in farmOS.py¶
Support for OAuth was added to farmOS.py starting with v0.1.6. To authorize and authenticate via OAuth, just supply the required parameters when creating a client. The library will know to use an OAuth Password Credentials or Authorization Code flow.
Password Credentials Flow:¶
farm_client = farmOS(
hostname=url,
username=farmos_username,
password=farmos_password,
client_id="farm",
scope="user_access",
# Supply a profile name so the tokens are saved to config
profile_name="farm"
)
Running from a Python Console, the password
can also be omitted and entered
at runtime. This allows testing without saving a password in plaintext:
>>> from farmOS import farmOS
>>> farm_client = farmOS(hostname="http://localhost", username=farmos_username, client_id="farm", scope="user_access")
>>> Warning: Password input may be echoed.
>>> Enter password: >? MY_PASSWORD
>>> farm_client.info()
'name': 'server-name', 'url': 'http://localhost', 'api_version': '1.2', 'user': ....
Authorization Code Flow:¶
It's also possible to run the Authorization Code Flow from the Python console.
This is great way to test the Authorization process users will go through. The
console will print a link to navigate to where you sign in to farmOS and
complete the authorization process. You then copy the link
from the page you
are redirected to back into the console. This supplies the farm_client
with
an an authorization code
that it uses to request an OAuth token
.
>>> farm_client = farmOS(hostname="http://localhost", client_id="farmos_development", scope="user_access")
Please go here and authorize, http://localhost/oauth/authorize?response_type=code&client_id=farmos_development&redirect_uri=http%3A%2F%2Flocalhost%2Fapi%2Fauthorized&scope=user_access&state=V9RCDd4yrSWZP8iGXt6qW51sYxsFZs&access_type=offline&prompt=select_account
Paste the full redirect URL here:>? http://localhost/api/authorized?code=33429f3530e36f4bdf3c2adbbfcd5b7d73e89d5c&state=V9RCDd4yrSWZP8iGXt6qW51sYxsFZs
>>> farm_client.info()
'name': 'server-name', 'url': 'http://localhost', 'api_version': '1.2', 'user': ....
Supplying an existing token:¶
It can also be useful to create a client with an existing OAuth Token. This is
possible by supplying the token
parameter:
token = {
'access_token': "token",
'refresh_token': "token",
}
farm_client = farmOS(
hostname=url,
client_id=client_id,
client_secret=client_secret,
scope=scope,
token=token,
)
This is how the farmOS Aggregator uses farmOS.py to communicate with farmOS servers. OAuth tokens are stored in the Aggregator's database instead of usernames and passwords. See how this is implemented in code here
Troubleshooting¶
Some common issues and solutions are described below. If these do not help, submit a support request on GitHub or ask questions in the farmOS chat room.
- HTTPS redirects - If your farmOS instance automatically redirects from
http://
tohttps://
, make sure you are making requests using thehttps://
protocol. The redirect can cause issues with some API requests. - Date format - All dates in farmOS are stored in Unix timestamp format. Most frameworks/languages provide functions for generating/converting dates/times to this format. Only Unix timestamps will be accepted by the API.