%%%------------------------------------------------------------------- %%% File : ejabberd_oauth_rest.erl %%% Author : Alexey Shchepin %%% Purpose : OAUTH2 REST backend %%% Created : 26 Jul 2016 by Alexey Shchepin %%% %%% %%% ejabberd, Copyright (C) 2002-2024 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as %%% published by the Free Software Foundation; either version 2 of the %%% License, or (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU %%% General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA %%% 02111-1307 USA %%% %%%------------------------------------------------------------------- -module(ejabberd_oauth_rest). -behaviour(ejabberd_oauth). -export([init/0, store/1, lookup/1, clean/1, lookup_client/1, store_client/1, revoke/1]). -include("ejabberd_oauth.hrl"). -include("logger.hrl"). -include_lib("xmpp/include/jid.hrl"). init() -> rest:start(ejabberd_config:get_myname()), ok. store(R) -> Path = path(<<"store">>), %% Retry 2 times, with a backoff of 500millisec {User, Server} = R#oauth_token.us, SJID = jid:encode({User, Server, <<"">>}), case rest:with_retry( post, [ejabberd_config:get_myname(), Path, [], #{<<"token">> => R#oauth_token.token, <<"user">> => SJID, <<"scope">> => R#oauth_token.scope, <<"expire">> => R#oauth_token.expire }], 2, 500) of {ok, Code, _} when Code == 200 orelse Code == 201 -> ok; Err -> ?ERROR_MSG("Failed to store oauth record ~p: ~p", [R, Err]), {error, db_failure} end. lookup(Token) -> Path = path(<<"lookup">>), case rest:with_retry(post, [ejabberd_config:get_myname(), Path, [], #{<<"token">> => Token}], 2, 500) of {ok, 200, Data} -> SJID = case maps:find(<<"user">>, Data) of {ok, U} -> U; error -> <<>> end, JID = jid:decode(SJID), US = {JID#jid.luser, JID#jid.lserver}, Scope = case maps:find(<<"scope">>, Data) of {ok, S} -> S; error -> [] end, Expire = case maps:find(<<"expire">>, Data) of {ok, E} -> E; error -> 0 end, {ok, #oauth_token{token = Token, us = US, scope = Scope, expire = Expire}}; {ok, 404, _Resp} -> error; Other -> ?ERROR_MSG("Unexpected response for oauth lookup: ~p", [Other]), case ejabberd_option:oauth_cache_rest_failure_life_time() of infinity -> error; Time -> {cache_with_timeout, error, Time} end end. -spec revoke(binary()) -> ok | {error, binary()}. revoke(_Token) -> {error, <<"not available">>}. clean(_TS) -> ok. path(Path) -> Base = ejabberd_option:ext_api_path_oauth(), <>. store_client(#oauth_client{client_id = ClientID, client_name = ClientName, grant_type = GrantType, options = Options} = R) -> Path = path(<<"store_client">>), SGrantType = case GrantType of password -> <<"password">>; implicit -> <<"implicit">> end, SOptions = misc:term_to_base64(Options), %% Retry 2 times, with a backoff of 500millisec case rest:with_retry( post, [ejabberd_config:get_myname(), Path, [], #{<<"client_id">> => ClientID, <<"client_name">> => ClientName, <<"grant_type">> => SGrantType, <<"options">> => SOptions }], 2, 500) of {ok, Code, _} when Code == 200 orelse Code == 201 -> ok; Err -> ?ERROR_MSG("Failed to store oauth record ~p: ~p", [R, Err]), {error, db_failure} end. lookup_client(ClientID) -> Path = path(<<"lookup_client">>), case rest:with_retry(post, [ejabberd_config:get_myname(), Path, [], #{<<"client_id">> => ClientID}], 2, 500) of {ok, 200, Data} -> ClientName = case maps:find(<<"client_name">>, Data) of {ok, CN} -> CN; error -> <<>> end, SGrantType = case maps:find(<<"grant_type">>, Data) of {ok, GT} -> GT; error -> <<>> end, GrantType = case SGrantType of <<"password">> -> password; <<"implicit">> -> implicit end, SOptions = case maps:find(<<"options">>, Data) of {ok, O} -> O; error -> <<>> end, case misc:base64_to_term(SOptions) of {term, Options} -> {ok, #oauth_client{client_id = ClientID, client_name = ClientName, grant_type = GrantType, options = Options}}; _ -> error end; {ok, 404, _Resp} -> error; Other -> ?ERROR_MSG("Unexpected response for oauth lookup: ~p", [Other]), error end.