Current section

Files

Jump to
oidcc src oidcc_client.erl
Raw

src/oidcc_client.erl

-module(oidcc_client).
-behaviour(gen_server).
%% API
-export([start_link/0]).
-export([stop/0]).
-export([succeeded/2]).
-export([succeeded/3]).
-export([failed/3]).
-export([failed/4]).
-export([register/1]).
-export([get_module/1]).
-callback login_succeeded(Token :: map()) -> {ok, [term()]}.
-callback login_failed(Reason :: atom(), Description :: binary()) -> {ok, [term()]}.
%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).
-record(state, {ets_mod = undefined, ets_id = undefined}).
%% API.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
stop() ->
gen_server:cast(?MODULE, stop).
register(Module) when is_atom(Module) ->
List = Module:module_info(exports),
SucceededOne = lists:member({login_succeeded, 1}, List),
SucceededTwo = lists:member({login_succeeded, 2}, List),
FailedTwo = lists:member({login_failed, 2}, List),
FailedThree = lists:member({login_failed, 3}, List),
true = SucceededOne or SucceededTwo,
true = FailedTwo or FailedThree,
gen_server:call(?MODULE, {add_module, Module}).
succeeded(Token, ModuleId) ->
succeeded(Token, ModuleId, #{}).
succeeded(Token, ModuleId, Environment) when is_map(Environment) ->
{ok, Mod} = get_module(ModuleId),
{ok, Updates} = call_succeeded(Mod, Token, Environment),
reorder_updates(Updates).
failed(Error, Description, ModuleId) ->
failed(Error, Description, ModuleId, #{}).
failed(Error, Description, ModuleId, Environment) when is_map(Environment) ->
{ok, Mod} = get_module(ModuleId),
{ok, Updates} = call_failed(Mod, Error, Description, Environment),
reorder_updates(Updates).
get_module(Id) ->
Ets = oidcc_ets_client_id,
case ets:lookup(Ets, Id) of
[{Id, Mod}] ->
{ok, Mod};
_ ->
[{default, DefMod}] = ets:lookup(Ets, default),
{ok, DefMod}
end.
init(_) ->
EtsId = ets:new(oidcc_ets_client_id, [set, protected, named_table]),
EtsMod = ets:new(oidcc_ets_client_mod, [set, protected]),
{ok, #state{ets_id = EtsId, ets_mod = EtsMod}}.
handle_call({add_module, Module}, _From, State) ->
{ok, Id} = add_module(Module, State),
{reply, {ok, Id}, State};
handle_call(_Request, _From, State) ->
{reply, ignored, State}.
handle_cast(stop, State) ->
{stop, normal, State};
handle_cast(_Request, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
add_module(Module, #state{ets_mod = EtsMod, ets_id = EtsId}) ->
case ets:lookup(EtsMod, Module) of
[{Module, Id}] ->
{ok, Id};
[] ->
insert_new_module(Module, EtsId, EtsMod)
end.
insert_new_module(Module, EtsId, EtsMod) ->
Id = random_string(9),
InsertResult = ets:insert_new(EtsId, {Id, Module}),
handle_insert_result(InsertResult, EtsMod, Module, EtsId, Id).
handle_insert_result(true, EtsMod, Module, EtsId, Id) ->
true = ets:insert_new(EtsMod, {Module, Id}),
Default = ets:lookup(EtsId, default),
set_default_if_needed(Default, Module, EtsId),
{ok, Id};
handle_insert_result(_, EtsMod, Module, EtsId, _Id) ->
insert_new_module(Module, EtsId, EtsMod).
set_default_if_needed([], Module, Ets) ->
true = ets:insert_new(Ets, {default, Module}),
ok;
set_default_if_needed(_, _Module, _Ets) ->
ok.
reorder_updates(Updates) ->
append_redirect(lists:keyfind(redirect, 1, Updates), Updates).
append_redirect(false, Updates) ->
{ok, Updates};
append_redirect(Tuple, Updates) ->
NewUpdates = lists:keydelete(redirect, 1, Updates),
OrderedUpdates = NewUpdates ++ [Tuple],
{ok, OrderedUpdates}.
random_string(Length) ->
base64url:encode(
crypto:strong_rand_bytes(Length)).
call_succeeded(Mod, Token, Environment) ->
Exports = Mod:module_info(exports),
SucceededTwo = lists:member({login_succeeded, 2}, Exports),
call_matching_succeeded(SucceededTwo, Mod, Token, Environment).
call_matching_succeeded(true, Mod, Token, Environment) ->
Mod:login_succeeded(Token, Environment);
call_matching_succeeded(_, Mod, Token, _) ->
Mod:login_succeeded(Token).
call_failed(Mod, Error, Description, Environment) ->
Exports = Mod:module_info(exports),
FailedThree = lists:member({login_failed, 3}, Exports),
call_matching_failed(FailedThree, Mod, Error, Description, Environment).
call_matching_failed(true, Mod, Error, Description, Environment) ->
Mod:login_failed(Error, Description, Environment);
call_matching_failed(_, Mod, Error, Description, _) ->
Mod:login_failed(Error, Description).