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([failed/3]).
-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),
true = lists:member({login_succeeded, 1}, List),
true = lists:member({login_failed, 2}, List),
gen_server:call(?MODULE, {add_module, Module}).
succeeded(Token, ModuleId) ->
{ok, Mod} = get_module(ModuleId),
{ok, Updates} = Mod:login_succeeded(Token),
reorder_updates(Updates).
failed(Error, Description, ModuleId) ->
{ok, Mod} = get_module(ModuleId),
{ok, Updates} = Mod:login_failed(Error, Description),
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),
case ets:insert_new(EtsId, {Id, Module}) of
true ->
true = ets:insert_new(EtsMod, {Module, Id}),
Default = ets:lookup(EtsId, default),
set_default_if_needed(Default, Module, EtsId),
{ok, Id};
_ ->
insert_new_module(Module, EtsId, EtsMod)
end.
set_default_if_needed([], Module, Ets) ->
true = ets:insert_new(Ets, {default, Module}),
ok;
set_default_if_needed(_, _Module, _Ets) ->
ok.
reorder_updates(Updates) ->
case lists:keyfind(redirect, 1, Updates) of
false -> {ok, Updates};
Tuple -> NewUpdates = lists:keydelete(redirect, 1, Updates),
OrderedUpdates = NewUpdates ++ [Tuple],
{ok, OrderedUpdates}
end.
random_string(Length) ->
base64url:encode(crypto:strong_rand_bytes(Length)).