Current section

Files

Jump to
oidcc src oidcc_openid_provider_mgr.erl
Raw

src/oidcc_openid_provider_mgr.erl

-module(oidcc_openid_provider_mgr).
-behaviour(gen_server).
%% API.
-export([start_link/0]).
-export([stop/0]).
-export([add_openid_provider/1]).
-export([get_openid_provider/1]).
-export([find_openid_provider/1]).
-export([get_openid_provider_list/0]).
%% 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_prov = undefined,
ets_iss = undefined,
ets_mon = undefined
}).
%% API.
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
stop() ->
gen_server:cast(?MODULE, stop).
-spec add_openid_provider(Config::map()) ->
{ok, Id::binary(), pid()} | {error, Reason::atom()}.
add_openid_provider(Config) ->
Id = maps:get(id, Config, undefined),
gen_server:call(?MODULE, {add_provider, Id, Config}).
get_openid_provider(Id) ->
get_provider(Id).
get_openid_provider_list() ->
get_provider_list().
-spec find_openid_provider(Issuer::binary()) -> {ok, pid()}
| {error, not_found}.
find_openid_provider(Issuer) ->
find_provider(Issuer).
%% gen_server.
init([]) ->
ProvEts = ets:new(oidcc_ets_provider, [set, protected, named_table]),
IssEts = ets:new(oidcc_ets_issuer, [set, protected, named_table]),
MonEts = ets:new(oidcc_ets_monitor, [set, protected]),
{ok, #state{ets_prov=ProvEts, ets_iss=IssEts, ets_mon = MonEts}}.
handle_call({add_provider, undefined, Config}, _From, State) ->
add_provider(Config, State);
handle_call({add_provider, Id, Config}, _From, State) ->
try_adding_provider(Id, Config, State);
handle_call(_Request, _From, State) ->
{reply, ignored, State}.
handle_cast(stop, State) ->
{stop, normal, State};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({'DOWN', MRef, process, _Object, _Info},
#state{ets_mon=MonEts, ets_prov=ProvEts, ets_iss=IssEts} = State) ->
case ets:lookup(MonEts, MRef) of
[{MRef, Id, Issuer}] ->
[Issuer1, Issuer2] = to_issuer(Issuer),
true = ets:delete(MonEts, MRef),
true = ets:delete(ProvEts, Id),
true = ets:delete(IssEts, Issuer1),
true = ets:delete(IssEts, Issuer2),
ok;
_ -> ok
end,
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
try_adding_provider(Id, Config, State) ->
case is_unique_id(Id, State) of
true -> add_provider(Id, Config, State);
false -> {reply, {error, id_already_used}, State}
end.
add_provider(Config, State) ->
Id = get_unique_id(State),
add_provider(Id, Config, State).
add_provider(Id, Config, State) ->
{ok, Pid} = start_provider(Id, Config),
IssuerOrEndpoint = maps:get(issuer_or_endpoint, Config),
ok = insert_provider(Id, IssuerOrEndpoint, Pid, State),
{reply, {ok, Id, Pid}, State}.
get_provider_list() ->
Ets = oidcc_ets_provider,
true = ets:safe_fixtable(Ets, true),
Last = ets:first(Ets),
List = create_provider_list(Last, [], Ets),
true = ets:safe_fixtable(Ets, false),
{ok, List}.
get_provider(Id) ->
case ets:lookup(oidcc_ets_provider, Id) of
[{Id, _Issuer, Pid, _MRef}] -> {ok, Pid};
_ -> {error, not_found}
end.
find_provider(Issuer) ->
Ets = oidcc_ets_issuer,
case ets:lookup(Ets, Issuer) of
[{Issuer, Pid}] ->
{ok, Pid};
_ ->
{error, not_found}
end.
start_provider(Id, Config) ->
oidcc_openid_provider_sup:add_openid_provider(Id, Config).
insert_provider(Id, IssuerOrEndpoint, Pid,
#state{ets_prov=ProvEts, ets_iss=IssEts, ets_mon=MonEts}) ->
MRef = monitor(process, Pid),
%% {ok, Issuer} = oidcc_openid_provider:get_issuer(Pid),
[Issuer1, Issuer2] = to_issuer(IssuerOrEndpoint),
true = ets:insert(ProvEts, {Id, Issuer1, Pid, MRef}),
true = ets:insert(IssEts, {Issuer1, Pid}),
true = ets:insert(IssEts, {Issuer2, Pid}),
true = ets:insert(MonEts, {MRef, Id, Issuer1}),
ok.
create_provider_list('$end_of_table', List, _) ->
lists:reverse(List);
create_provider_list(Current , List, Ets) ->
[{Id, _Iss, Pid, _MRef}] = ets:lookup(Ets, Current),
Next = ets:next(Ets, Current),
create_provider_list(Next, [{Id, Pid} | List], Ets).
get_unique_id(State) ->
Id = random_id(),
case is_unique_id(Id, State) of
true -> Id;
false -> get_unique_id(State)
end.
is_unique_id(Id, #state{ets_prov=Ets}) ->
case ets:lookup(Ets, Id) of
[] -> true;
_ -> false
end.
random_id() ->
random_id(5).
random_id(Length) ->
Random = try crypto:strong_rand_bytes(Length) of
Data -> Data
catch
low_entropy ->
timer:sleep(100),
random_id(Length)
end,
base64url:encode(Random).
to_issuer(IssuerOrEndpoint) ->
Slash = <<"/">>,
Config = <<".well-known/openid-configuration">>,
ConfigS = << Slash/binary, Config/binary >>,
Issuer = case binary:match(IssuerOrEndpoint, ConfigS) of
{Pos, 33} ->
binary:part(IssuerOrEndpoint, 0, Pos);
_ ->
case binary:last(IssuerOrEndpoint) of
$/ ->
Len = byte_size(IssuerOrEndpoint),
binary:part(IssuerOrEndpoint, 0, Len-1);
_ ->
IssuerOrEndpoint
end
end,
[Issuer, <<Issuer/binary, Slash/binary>>]
.