Openstack
Openstack leverages Token Authentication and we can use hooks to drastically simplify this as compared to the previous v103 Openstack Example. The following are sample request/response payloads from an Openstack server. We will use this information to build out the hooks needed.
The following shows an example of making a request to the Openstack token server and the response:
curl -i \
-H "Content-Type: application/json" \
-d '
{ "auth": {
"identity": {
"methods": ["password"],
"password": {
"user": {
"name": "admin",
"domain": { "id": "default" },
"password": "adminpwd"
}
}
}
}
}' \
"http://localhost:5000/v3/auth/tokens" ; echo
Example response:
HTTP/1.1 201 Created
X-Subject-Token: MIIFvgY...
Vary: X-Auth-Token
Content-Type: application/json
Content-Length: 312
Date: Fri, 11 May 2018 03:15:01 GMT
{
"token": {
"issued_at": "2018-05-11T03:15:01.000000Z",
"audit_ids": [
"0PKh_BDKTWqqaFONE-Sxbg"
],
"methods": [
"password"
],
"expires_at": "2018-05-11T04:15:01.000000Z",
"user": {
"password_expires_at": null,
"domain": {
"id": "default",
"name": "Default"
},
"id": "9a7e43333cc44ef4b988f05fc3d3a49d",
"name": "admin"
}
}
}
The payload is significantly different then what the out of the box Token
Authenticator supports so based on this, the first hook needed will be the
get_token_request_args
since it can be used to format the payload as above.
This function can modify ANY parameter in requests.Session.request
and in
this case the payload needs to be set as above. Since the payload is json the
parameter we will need to modify is the json
parameter. It should be noted
that setting the json
parameter causes the request to automatically insert the
Content-Type: application/json
header which is needed as shown above.
Thus, all that is needed is to return the payload that to requests. In this case only the credential parameter is required as the username and password from the credential are required (Refer to Credential Fields for all the available fields). Any other parameters defined in requests.Session.request can be modified.
For example
payload={“method”: “post”,”headers” : { “Content-Type”: “Application json”}, “json”: {…}}
can be used to set the method and include additional headers if they were needed.
The next problem is that the token response is NOT in the payload but rather in
the header. Additionally, the expiry information is a fixed date and thus that
will need to be converted into seconds. Therefore, the modify_response
hook
will be needed to retrieve the token from the header and to calculate the token
expiry. There are two input parameters needed for this case the response and the
refresh_info
. The response is a requests.response
as described in the link.
The refresh_info
includes the type of refresh and the value for the time to
live between token refreshes. That is refresh_info.type
indicates either
“tokenAuthRefreshStatic” or “tokenAuthRefreshDynamic”. This info comes from the
credential. The refresh_info.info.ttl
is also from the credential if static
refresh was selected. Otherwise it will default to 3600. The code puts the expiry
timer handling in a try block such that even if an error occurs the token refresh
will be set to the value in the refresh_info.info.ttl
which has a default
value of 3600 seconds.
Finally, the token itself is received in the X-Subject-Token
header and thus
needs to be retrieved from the response.headers
as shown in the code.
The system then continues and since the default behavior for the Token
Authenticator is that the token received is then used to make subsequent calls
with the token being included in a header whose definition is defined in the
credential. Since this behaviour is needed for openstack as long as the
Authorization Header
field is set to X-Auth-Token
then no further hooks
are required.
Complete Snippet Code
from silo.apps.errors import error_manager
with error_manager(self):
from silo.low_code import *
from silo.apps.collection import create_collections, save_collections
# =====================================
# =========== User Editable ===========
# =====================================
# List any custom substitutions that need to occur within the snippet arguments
custom_substitution = {}
from silo.auth import create_token_authenticator
from silo.auth.base_token import *
from datetime import datetime, timezone
def get_token_request_args(credential):
payload={
"method" : "post",
"json": {
"auth": {
"identity": {
"methods": ["password"],
"password": {
"user": {
"name": credential.fields["username"],
"domain": { "id": "default" },
"password": credential.fields["password"]
}
}
}
}
}
}
return(payload)
def process_open_stack_response(response,refresh_info):
try:
if refresh_info.type=="tokenAuthRefreshDynamic":
exp_time=response.json()['token']['expires_at']
exp_time = exp_time.replace('Z', '')
exp_time = datetime.strptime(exp_time, '%Y-%m-%dT%H:%M:%S.%f')
exp_time = exp_time.replace(tzinfo=timezone.utc)
current_time = datetime.now(timezone.utc)
seconds= int((exp_time-current_time).total_seconds())
else:
seconds=refresh_info.info.ttl
except:
seconds=refresh_info.info.ttl
return token_response_fields(
expires_in=seconds,
token=response.headers["X-Subject-Token"],
)
Openstack = create_token_authenticator(
name="Openstack",
description="Openstack Authenticator.",
get_token_request_args= get_token_request_args,
process_response=process_open_stack_response
)
# =====================================
# ========= End User Editable =========
# =====================================
collections = create_collections(self)
snippet_framework(collections, custom_substitution, snippet_id, app=self)
save_collections(collections, self)
Credential used for Openstack
