OAuth2 Gmail

Added OAuth2 GMAIL loggin/logout support.
      Added OAuth2 Gmail filter.
LoginModule
      Updated LoginModule with OAuth2 Gmail support.
Persitence
      Extended user persitance api with OAuth2 gmail features.
      (TODO refactor persistance as an user extention)
This commit is contained in:
jvelilla
2015-06-08 12:58:33 -03:00
parent 181c32a895
commit 96ba3c35a2
12 changed files with 536 additions and 10 deletions

View File

@@ -0,0 +1,7 @@
{
"api_secret":"ADD_YOUR_SECRET_KEY",
"api_key":"ADD_YOUR_PUBLIC_KEY",
"scope": "email",
"api_revoke":"https://accounts.google.com/o/oauth2/revoke?token=$ACCESS_TOKEN",
"protected_resource_url":"https://www.googleapis.com/plus/v1/people/me"
}

View File

@@ -0,0 +1,14 @@
BEGIN;
CREATE TABLE "oauth2_gmail"(
"uid" INTEGER PRIMARY KEY NOT NULL CHECK("uid">=0),
"access_token" VARCHAR(255) NOT NULL,
"created" DATETIME NOT NULL,
"details" TEXT NOT NULL,
CONSTRAINT "uid"
UNIQUE("uid")
);
COMMIT;

View File

@@ -1,6 +1,6 @@
<div> <div>
{if isset="$user"} {if isset="$user"}
<h3><a href="/basic_auth_logoff">Logout</a> </h3> <h3><a href="/roc-logout">Logout</a> </h3>
{/if} {/if}
{unless isset="$user"} {unless isset="$user"}
<h3>Login or <a href="/roc-register">Register</a></h3> <h3>Login or <a href="/roc-register">Register</a></h3>
@@ -28,5 +28,8 @@
</p> </p>
</div> </div>
</div> </div>
{/unless} {/unless}
<div>
<a href="/login-with-google"><img src="http://qpleple.com/img/post-how-to-make-people-login-into-your-website-with-their-google-account/signin-google-3.png"></a>
</div>
</div> </div>

View File

@@ -0,0 +1,46 @@
note
description: "Summary description for {OAUTH_GMAIL_FILTER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
OAUTH_GMAIL_FILTER
inherit
WSF_URI_TEMPLATE_HANDLER
CMS_HANDLER
WSF_FILTER
create
make
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute the filter.
local
utf: UTF_CONVERTER
do
api.logger.put_debug (generator + ".execute ", Void)
-- if attached req.raw_header_data as l_raw_data then
-- api.logger.put_debug (generator + ".execute " + utf.escaped_utf_32_string_to_utf_8_string_8 (l_raw_data), Void)
-- end
-- A valid user
if
attached {WSF_STRING} req.cookie ("EWF_ROC_OAUTH_GMAIL_SESSION_") as l_roc_auth_session_token
then
if attached {CMS_USER} api.user_api.user_by_oauth2_gmail_token (l_roc_auth_session_token.value) as l_user then
set_current_user (req, l_user)
execute_next (req, res)
else
api.logger.put_error (generator + ".execute login_valid failed for: " + l_roc_auth_session_token.value , Void)
execute_next (req, res)
end
else
api.logger.put_debug (generator + ".execute without authentication", Void)
execute_next (req, res)
end
end
end

View File

@@ -18,6 +18,12 @@
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/> <library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/> <library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/> <library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
<library name="apis" location="..\..\..\cypress_jv\consumer\apis\apis.ecf" readonly="false"/>
<library name="cypress_consumer" location="..\..\..\cypress_jv\consumer\cypress_consumer-safe.ecf" readonly="false"/>
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json-safe.ecf" readonly="false"/>
<cluster name="src" location=".\" recursive="true"/> <cluster name="src" location=".\" recursive="true"/>
</target> </target>
</system> </system>

View File

@@ -9,6 +9,7 @@ class
inherit inherit
CMS_MODULE CMS_MODULE
redefine redefine
filters,
register_hooks register_hooks
end end
@@ -48,6 +49,16 @@ feature {NONE} -- Initialization
cache_duration := 0 cache_duration := 0
end end
feature -- Filters
filters (a_api: CMS_API): detachable LIST [WSF_FILTER]
-- Possibly list of Filter's module.
do
create {ARRAYED_LIST [WSF_FILTER]} Result.make (1)
Result.extend (create {OAUTH_GMAIL_FILTER}.make (a_api))
end
feature -- Access: docs feature -- Access: docs
root_dir: PATH root_dir: PATH
@@ -75,6 +86,8 @@ feature -- Router
a_router.handle_with_request_methods ("/new-password", create {WSF_URI_AGENT_HANDLER}.make (agent handle_new_password (a_api, ?, ?)), a_router.methods_get_post) a_router.handle_with_request_methods ("/new-password", create {WSF_URI_AGENT_HANDLER}.make (agent handle_new_password (a_api, ?, ?)), a_router.methods_get_post)
a_router.handle_with_request_methods ("/reset-password", create {WSF_URI_AGENT_HANDLER}.make (agent handle_reset_password (a_api, ?, ?)), a_router.methods_get_post) a_router.handle_with_request_methods ("/reset-password", create {WSF_URI_AGENT_HANDLER}.make (agent handle_reset_password (a_api, ?, ?)), a_router.methods_get_post)
a_router.handle_with_request_methods ("/roc-logout", create {WSF_URI_AGENT_HANDLER}.make (agent handle_logout (a_api, ?, ?)), a_router.methods_get_post) a_router.handle_with_request_methods ("/roc-logout", create {WSF_URI_AGENT_HANDLER}.make (agent handle_logout (a_api, ?, ?)), a_router.methods_get_post)
a_router.handle_with_request_methods ("/login-with-google", create {WSF_URI_AGENT_HANDLER}.make (agent handle_login_with_google (a_api, ?, ?)), a_router.methods_get_post)
a_router.handle_with_request_methods ("/oauthgmail", create {WSF_URI_AGENT_HANDLER}.make (agent handle_callback_gmail (a_api, ?, ?)), a_router.methods_get_post)
end end
feature -- Hooks configuration feature -- Hooks configuration
@@ -171,15 +184,33 @@ feature -- Hooks
handle_logout (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE) handle_logout (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local local
r: CMS_RESPONSE r: CMS_RESPONSE
br: BAD_REQUEST_ERROR_CMS_RESPONSE
l_url: STRING l_url: STRING
l_oauth_gmail: OAUTH_LOGIN_GMAIL
l_cookie: WSF_COOKIE
do do
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) if
r.set_status_code ({HTTP_CONSTANTS}.found) attached {WSF_STRING} req.cookie ("EWF_ROC_OAUTH_GMAIL_SESSION_") as l_cookie_token and then
l_url := req.absolute_script_url ("") attached {CMS_USER} current_user (req) as l_user
l_url.append ("/basic_auth_logoff") then
r.set_redirection (l_url) -- Logout gmail
r.execute create l_oauth_gmail.make (api, req.absolute_script_url (""))
l_oauth_gmail.sign_out (l_cookie_token.value)
create l_cookie.make ("EWF_ROC_OAUTH_GMAIL_SESSION_", l_cookie_token.value)
l_cookie.set_max_age (-1)
res.add_cookie (l_cookie)
unset_current_user (req)
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
r.set_status_code ({HTTP_CONSTANTS}.found)
r.set_redirection (req.absolute_script_url (""))
r.execute
else
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
r.set_status_code ({HTTP_CONSTANTS}.found)
l_url := req.absolute_script_url ("")
l_url.append ("/basic_auth_logoff")
r.set_redirection (l_url)
r.execute
end
end end
handle_register (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE) handle_register (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
@@ -606,6 +637,92 @@ feature {NONE} -- Block views
end end
end end
feature -- OAuth2 Login with google.
handle_login_with_google (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
l_oauth_gmail: OAUTH_LOGIN_GMAIL
do
create l_oauth_gmail.make (api, req.absolute_script_url (""))
if attached l_oauth_gmail.authorization_url as l_authorization then
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
r.set_redirection (l_authorization)
r.execute
else
create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, api)
r.set_main_content ("Bad request")
r.execute
end
end
handle_callback_gmail (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
l_auth_gmail: OAUTH_LOGIN_GMAIL
l_user_api: CMS_USER_API
l_user: CMS_USER
l_roles: LIST [CMS_USER_ROLE]
l_cookie: WSF_COOKIE
do
if attached {WSF_STRING} req.query_parameter ("code") as l_code then
create l_auth_gmail.make (api, req.server_url)
l_auth_gmail.sign_request (l_code.value)
if
attached l_auth_gmail.access_token as l_access_token and then
attached l_auth_gmail.user_profile as l_user_profile
then
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
-- extract user email
-- check if the user exist
l_user_api := api.user_api
-- 1 if the user exit put it in the context
if
attached l_auth_gmail.user_email as l_email
then
if attached {CMS_USER} l_user_api.user_by_email (l_email) as p_user then
-- User with email exist
if attached {CMS_USER} l_user_api.user_oauth2_gmail_by_id (p_user.id) then
-- Update oauth entry
l_user_api.update_user_oauth2_gmail (l_access_token.token, l_user_profile, p_user )
else
-- create a oauth entry
l_user_api.new_user_oauth2_gmail (l_access_token.token, l_user_profile, p_user )
end
create l_cookie.make ("EWF_ROC_OAUTH_GMAIL_SESSION_", l_access_token.token)
l_cookie.set_max_age (l_access_token.expires_in)
res.add_cookie (l_cookie)
else
create {ARRAYED_LIST [CMS_USER_ROLE]}l_roles.make (1)
l_roles.force (l_user_api.authenticated_user_role)
-- Create a new user and oauth entry
create l_user.make (l_email)
l_user.set_email (l_email)
l_user.set_password (new_token) -- generate a random password.
l_user.set_roles (l_roles)
l_user.mark_active
l_user_api.new_user (l_user)
-- Add oauth entry
l_user_api.new_user_oauth2_gmail (l_access_token.token, l_user_profile, l_user )
create l_cookie.make ("EWF_ROC_OAUTH_GMAIL_SESSION_", l_access_token.token)
l_cookie.set_max_age (l_access_token.expires_in)
res.add_cookie (l_cookie)
end
else
end
r.set_redirection (req.absolute_script_url (""))
r.execute
end
end
end
feature {NONE} -- Token Generation feature {NONE} -- Token Generation
new_token: STRING new_token: STRING

View File

@@ -0,0 +1,179 @@
note
description: "OAuth workflow for Gmails."
date: "$Date$"
revision: "$Revision$"
class
OAUTH_LOGIN_GMAIL
inherit
SHARED_LOGGER
create
make
feature {NONE} -- Initialization
make (a_cms_api:CMS_API a_host: READABLE_STRING_32)
-- Create an object with the host `a_host'.
do
cms_api := a_cms_api
initilize
create config.make_default (api_key, api_secret)
config.set_callback (a_host + "/oauthgmail")
config.set_scope (scope)
create goauth
api_service := goauth.create_service (config)
ensure
cms_api_set: cms_api = a_cms_api
end
initilize
local
utf: UTF_CONVERTER
do
--Use configuration values if any if not defaul
api_key := "KEY"
api_secret := "SECRET"
scope := "email"
api_revoke := "[https://accounts.google.com/o/oauth2/revoke?token=$ACCESS_TOKEN]"
protected_resource_url := "https://www.googleapis.com/plus/v1/people/me"
if attached {CONFIG_READER} cms_api.module_configuration ("login", "oauth2_gmail") as cfg then
if attached cfg.text_item ("api_secret") as l_api_secret then
api_secret := utf.utf_32_string_to_utf_8_string_8 (l_api_secret)
end
if attached cfg.text_item ("api_key") as l_api_key then
api_key := utf.utf_32_string_to_utf_8_string_8 (l_api_key)
end
if attached cfg.text_item ("scope") as l_scope then
scope := utf.utf_32_string_to_utf_8_string_8 (l_scope)
end
if attached cfg.text_item ("api_revoke") as l_api_revoke then
api_revoke := utf.utf_32_string_to_utf_8_string_8 (l_api_revoke)
end
if attached cfg.text_item ("protected_resource_url") as l_resource_url then
protected_resource_url := utf.utf_32_string_to_utf_8_string_8 (l_resource_url)
end
end
end
feature -- Access
authorization_url: detachable READABLE_STRING_32
-- Obtain the Authorization URL.
do
-- Obtain the Authorization URL
write_debug_log (generator + ".authorization_url Fetching the Authorization URL..!")
if attached api_service.authorization_url (empty_token) as l_authorization_url then
write_debug_log (generator + ".authorization_url: Got the Authorization URL!")
write_debug_log (generator + ".authorization_url:" + l_authorization_url)
Result := l_authorization_url.as_string_32
end
end
sign_request (a_code: READABLE_STRING_32)
-- Sign request with code `a_code'.
--! To get the code `a_code' you need to do a request
--! using the authorization_url
local
request: OAUTH_REQUEST
do
-- Get the access token.
write_debug_log (generator + ".sign_request Fetching the access token with code [" + a_code + "]")
access_token := api_service.access_token_post (empty_token, create {OAUTH_VERIFIER}.make (a_code))
if attached access_token as l_access_token then
write_debug_log (generator + ".sign_request Got the Access Token [" + l_access_token.debug_output + "]")
-- Get the user email
--! at the moment the scope is mail, but we can change it to get more information.
create request.make ("GET", protected_resource_url)
request.add_header ("Authorization", "Bearer " + l_access_token.token)
api_service.sign_request (l_access_token, request)
if attached {OAUTH_RESPONSE} request.execute as l_response then
write_debug_log (generator + ".sign_request Sign_request response [" + l_response.status.out + "]")
if attached l_response.body as l_body then
user_profile := l_body
write_debug_log (generator + ".sign_request User profile [" + l_body + "]")
end
end
end
end
sign_out (a_code: READABLE_STRING_32)
-- Invalidate the current OAuth access token `a_code'.
local
l_revoke: STRING
request: OAUTH_REQUEST
do
create l_revoke.make_from_string (api_revoke)
l_revoke.replace_substring_all ("$ACCESS_TOKEN", a_code)
create request.make ("POST", l_revoke)
if attached {OAUTH_RESPONSE} request.execute as l_response then
-- do nothing
write_debug_log (generator + ".sign_out response [" + l_response.status.out + "]")
check invalidate_ok: l_response.status = {HTTP_CONSTANTS}.ok end
end
end
user_email: detachable READABLE_STRING_32
-- Retrieve user email if any.
local
l_json: JSON_CONFIG
do
if attached user_profile as l_profile then
create l_json.make_from_string (l_profile)
if
attached {JSON_ARRAY} l_json.item ("emails") as l_array and then
attached {JSON_OBJECT} l_array.i_th (1) as l_object and then
attached {JSON_STRING} l_object.item ("value") as l_email
then
Result := l_email.item
end
end
end
feature -- Access
access_token: detachable OAUTH_TOKEN
-- JSON representing the access token.
user_profile: detachable READABLE_STRING_32
-- JSON representing the user profiles.
feature {NONE} -- Implementation
goauth: OAUTH_20_GOOGLE_API
-- OAuth 2.0 Google API.
config: OAUTH_CONFIG
-- configuration.
api_service: OAUTH_SERVICE_I
-- Service.
api_key: STRING
-- public key.
api_secret: STRING
-- secret key.
scope: STRING
-- api scope to access protected resources.
api_revoke: STRING
-- Revoke url
protected_resource_url: STRING
-- Resource url.
empty_token: detachable OAUTH_TOKEN
-- fake token.
cms_api: CMS_API
-- CMS API.
end

View File

@@ -178,4 +178,25 @@ feature -- Change: User password recovery
-- <Precursor>. -- <Precursor>.
deferred deferred
end end
feature -- Change: User Oauth2
new_user_oauth2_gmail (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER)
-- Add a new user with oauth2 gmail authentication.
deferred
end
update_user_oauth2_gmail (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER)
-- Update user `a_user' with oauth2 gmail authentication.
deferred
end
user_oauth2_gmail_by_id (a_uid: like {CMS_USER}.id): detachable CMS_USER
deferred
end
user_by_oauth2_gmail_token (a_token: READABLE_STRING_32): detachable CMS_USER
deferred
end
end end

View File

@@ -107,4 +107,24 @@ feature -- Change: User password recovery
-- <Precursor>. -- <Precursor>.
do do
end end
feature -- Change User Oauth
new_user_oauth2_gmail (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER)
-- Add a new user with oauth2 gmail authentication.
do
end
update_user_oauth2_gmail (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER)
-- Update user `a_user' with oauth2 gmail authentication.
do
end
user_oauth2_gmail_by_id (a_uid: like {CMS_USER}.id): detachable CMS_USER
do
end
user_by_oauth2_gmail_token (a_token: READABLE_STRING_32): detachable CMS_USER
do
end
end end

View File

@@ -579,6 +579,82 @@ feature -- Change: User password recovery
end end
feature -- User Oauth2
new_user_oauth2_gmail (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER)
-- Add a new user with oauth2 gmail authentication.
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
sql_begin_transaction
write_information_log (generator + ".new_user_oauth2_gmail")
create l_parameters.make (4)
l_parameters.put (a_user.id, "uid")
l_parameters.put (a_token, "token")
l_parameters.put (a_user_profile, "profile")
l_parameters.put (create {DATE_TIME}.make_now_utc, "utc_date")
sql_change (sql_insert_oauth2_gmail, l_parameters)
sql_commit_transaction
end
update_user_oauth2_gmail (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER)
-- Add a new user with oauth2 gmail authentication.
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
sql_begin_transaction
write_information_log (generator + ".new_user_oauth2_gmail")
create l_parameters.make (4)
l_parameters.put (a_user.id, "uid")
l_parameters.put (a_token, "token")
l_parameters.put (a_user_profile, "profile")
sql_change (sql_update_oauth2_gmail, l_parameters)
sql_commit_transaction
end
user_by_oauth2_gmail_token (a_token: READABLE_STRING_32): detachable CMS_USER
-- User for the given password token `a_token', if any.
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
write_information_log (generator + ".user_by_oauth2_gmail_token")
create l_parameters.make (1)
l_parameters.put (a_token, "token")
sql_query (select_user_by_oauth2_gmail_token, l_parameters)
if sql_rows_count = 1 then
Result := fetch_user
else
check no_more_than_one: sql_rows_count = 0 end
end
end
user_oauth2_gmail_by_id (a_uid: like {CMS_USER}.id): detachable CMS_USER
-- User for the given password token `a_token', if any.
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
write_information_log (generator + ".user_oauth2_gmail_by_id")
create l_parameters.make (1)
l_parameters.put (a_uid, "uid")
sql_query (select_user_oauth2_gmail_by_id, l_parameters)
if sql_rows_count = 1 then
Result := fetch_user
else
check no_more_than_one: sql_rows_count = 0 end
end
end
feature {NONE} -- Implementation: User feature {NONE} -- Implementation: User
user_salt (a_username: READABLE_STRING_32): detachable READABLE_STRING_8 user_salt (a_username: READABLE_STRING_32): detachable READABLE_STRING_8
@@ -743,7 +819,7 @@ feature {NONE} -- Sql Queries: USER ACTIVATION
Sql_remove_activation: STRING = "DELETE FROM users_activations WHERE token = :token;" Sql_remove_activation: STRING = "DELETE FROM users_activations WHERE token = :token;"
-- Remove activation token. -- Remove activation token.
feature {NONE} feature {NONE} -- User Password Recovery
sql_insert_password: STRING = "INSERT INTO users_password_recovery (token, uid, created) VALUES (:token, :uid, :utc_date);" sql_insert_password: STRING = "INSERT INTO users_password_recovery (token, uid, created) VALUES (:token, :uid, :utc_date);"
-- SQL insert a new password recovery :token. -- SQL insert a new password recovery :token.
@@ -754,6 +830,14 @@ feature {NONE}
Select_user_by_password_token: STRING = "SELECT u.* FROM users as u JOIN users_password_recovery as ua ON ua.uid = u.uid and ua.token = :token;" Select_user_by_password_token: STRING = "SELECT u.* FROM users as u JOIN users_password_recovery as ua ON ua.uid = u.uid and ua.token = :token;"
-- Retrieve user by password token if exist. -- Retrieve user by password token if exist.
feature {NONE}-- User Oauth2 Gmail.
Sql_insert_oauth2_gmail: STRING = "INSERT INTO oauth2_gmail (uid, access_token, details, created) VALUES (:uid, :token, :profile, :utc_date);"
Sql_update_oauth2_gmail: STRING = "UPDATE oauth2_gmail SET access_token = :token, details = :profile WHERE uid =:uid;"
Select_user_by_oauth2_gmail_token: STRING = "SELECT u.* FROM users as u JOIN oauth2_gmail as og ON og.uid = u.uid and og.access_token = :token;"
Select_user_oauth2_gmail_by_id: STRING = "SELECT u.* FROM users as u JOIN oauth2_gmail as og ON og.uid = u.uid and og.uid = :uid;"
end end

View File

@@ -151,6 +151,35 @@ feature -- Change User
storage.update_user (a_user) storage.update_user (a_user)
end end
new_user_oauth2_gmail (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER)
-- Add a new user with oauth2 gmail authentication.
require
has_id: a_user.has_id
do
storage.new_user_oauth2_gmail (a_token, a_user_profile, a_user)
end
update_user_oauth2_gmail (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER)
-- Updaate user `a_user' with oauth2 gmail authentication.
require
has_id: a_user.has_id
do
storage.update_user_oauth2_gmail (a_token, a_user_profile, a_user)
end
user_oauth2_gmail_by_id (a_uid: like {CMS_USER}.id): detachable CMS_USER
do
Result := storage.user_oauth2_gmail_by_id (a_uid)
end
user_by_oauth2_gmail_token (a_token: READABLE_STRING_32): detachable CMS_USER
do
Result := storage.user_by_oauth2_gmail_token (a_token)
end
feature -- User Activation feature -- User Activation
new_activation (a_token: READABLE_STRING_32; a_id: INTEGER_64) new_activation (a_token: READABLE_STRING_32; a_id: INTEGER_64)