<commit_before>-module(imem_seco).

-include("imem_seco.hrl").

-define(GET_PASSWORD_LIFE_TIME(__AccountId), ?GET_CONFIG(passwordLifeTime,[__AccountId],100)).
-define(SALT_BYTES, 32).
-define(PWD_HASH, scrypt).                       %% target hash: pwdmd5,md4,md5,sha512,scrypt 
-define(PWD_HASH_LIST, [scrypt,sha512,pwdmd5]).  %% allowed hash types
-define(REQUIRE_PWDMD5, <<"fun(Factors,NetCtx) -> [pwdmd5] -- Factors end">>).  % access | smsott | saml | pwdmd5
-define(AUTH_SMS_TOKEN_RETRY_DELAY, 1000).
-define(FULL_ACCESS, <<"fun(NetCtx) -> true end">>).

-behavior(gen_server).

-record(state, {
        }).

-export([ start_link/1
        ]).

% gen_server interface (monitoring calling processes)
%-export([ monitor/1
%        , cleanup_pid/1
%        ]).

% gen_server behavior callbacks
-export([ init/1
        , handle_call/3
        , handle_cast/2
        , handle_info/2
        , terminate/2
        , code_change/3
        , format_status/2
        ]).

% security context library interface
-export([ drop_seco_tables/1
        , create_credentials/2      % parameters: (Name,Password) or (<PASSWORD>,Password)
        ]).

-export([ authenticate/3            % deprecated
        , auth_start/3
        , auth_add_cred/2
        , auth_abort/1
        , login/1
        , change_credentials/3
        , set_credentials/3
        , set_login_time/2
        , logout/1
        , clone_seco/2
        , account_id/1
        , account_name/1
        ]).

-export([ has_role/3
        , has_permission/3
        ]).

-export([ have_role/2
        , have_permission/2
        ]).

monitor_pid(SKey,Pid) ->
    case if_select_seco_keys_by_pid(SKey,Pid) of
        {[],true} ->    % first registration for this pid, need to monitor
            try
                Ref = erlang:monitor(process, Pid),
                ?Info("Monitoring ~p for SKey ~p returns ~p", [Pid, SKey, Ref]),
                Ref
            catch 
                Class:Reason -> 
                    ?Warn("Monitoring ~p for SKey ~p failed with ~p:~p", [Pid, SKey, Class, Reason]),
                    {error,Reason}
            end;
        _ ->
            ok
    end.

start_link(Params) ->
    ?Info("~p starting...~n", [?MODULE]),
    case gen_server:start_link({local, ?MODULE}, ?MODULE, Params, [{spawn_opt, [{fullsweep_after, 0}]}]) of
        {ok, _} = Success ->
            ?Info("~p started!~n", [?MODULE]),
            Success;
        Error ->
            ?Error("~p failed to start ~p~n", [?MODULE, Error]),
            Error
    end.

init(_Args) ->
    try %% try creating system tables, may fail if they exist, then check existence 
        if_check_table(none, ddTable),

        ADef = {record_info(fields, ddAccount),?ddAccount,#ddAccount{}},
        catch imem_meta:create_check_table(ddAccount, ADef, [], system),
        imem_meta:create_or_replace_index(ddAccount, name),

        ADDef = {record_info(fields, ddAccountDyn),?ddAccountDyn,#ddAccountDyn{}},
        catch imem_meta:create_check_table(ddAccountDyn, ADDef, [], system),

        RDef = {record_info(fields, ddRole), ?ddRole, #ddRole{}},
        catch imem_meta:create_check_table(ddRole, RDef, [], system),

        SDef = {record_info(fields, ddSeCo), ?ddSeCo, #ddSeCo{}},
        case (catch imem_meta:table_columns(ddSeCo@)) of
            L when L==element(1,SDef) ->    ok;     % field names in table match the new record
            L when is_list(L) ->            ?Info("~p dropping old version of table ddSeCo@~n", [?MODULE]),
                                            imem_meta:drop_table(ddSeCo@);
            _ ->                            ok      % table does not exist
        end,

        catch imem_meta:create_check_table(ddSeCo@, SDef
              , [{scope,local}, {local_content,true},{record_name,ddSeCo}], system),

        PDef = {record_info(fields, ddPerm),?ddPerm, #ddPerm{}},
        catch imem_meta:create_check_table(ddPerm@, PDef
              , [{scope,local}, {local_content,true},{record_name,ddPerm}], system),

        QDef = {record_info(fields, ddQuota), ?ddQuota, #ddQuota{}},
        catch imem_meta:create_check_table(ddQuota@, QDef
              , [{scope,local}, {local_content,true},{record_name,ddQuota}], system),

        case if_select_account_by_name(none, <<"system">>) of
            {[],true} ->  
                    {ok, Pwd} = application:get_env(imem, default_admin_pswd),
                    LocalTime = calendar:local_time(),
                    UserCred=create_credentials(pwdmd5, Pwd),
                    Account = #ddAccount{id=system, type=deamon, name= <<"system">>, credentials=[UserCred]
                                , fullName= <<"DB Administrator">>, lastPasswordChangeTime=LocalTime},
                    AccountDyn = #ddAccountDyn{id=system},
                    if_write(none, ddAccount, Account),                    
                    if_write(none, ddAccountDyn, AccountDyn),                    
                    if_write(none, ddRole, #ddRole{id=system,roles=[],permissions=[manage_system, manage_accounts, manage_system_tables, manage_user_tables]});
            _ ->    ok
        end,
        % imem_meta:fail({"Fail in imem_seco:init on purpose"}),        
        if_truncate_table(none,ddSeCo@),
        if_truncate_table(none,ddPerm@),
        if_truncate_table(none,ddQuota@),

        process_flag(trap_exit, true),
        {ok,#state{}}
    catch
        _Class:Reason -> {stop, {Reason,erlang:get_stacktrace()}} 
    end.

% handle_call({monitor, Pid}, _From, State) ->
%     ?Info("~p - started monitoring pid ~p~n", [?MODULE, Pid]),
%     {reply, erlang:monitor(process, Pid), State};
handle_call(Request, From, State) ->
    ?Warn("Received unsolited call request ~p from  ~p", [Request,From]),
    {reply, ok, State}.

% handle_cast({stop, Reason}, State) ->
%     {stop,{shutdown,Reason},State};
handle_cast(Request, State) ->
    ?Warn("Received unsolited cast request ~p", [Request]),
    {noreply, State}.

handle_info({'DOWN', _Ref, process, Pid, normal}, State) ->
    ?Info("~p - received normal exit for monitored pid ~p ref ~p~n", [?MODULE, Pid, _Ref]),
    cleanup_pid(Pid),
    {noreply, State};
handle_info({'DOWN', _Ref, process, Pid, _Reason}, State) ->
    ?Info("~p - received unexpected exit for monitored pid ~p ref ~p reason ~p~n", [?MODULE, Pid, _Ref, _Reason]),
    cleanup_pid(Pid),
    {noreply, State};
handle_info(Info, State) ->
    ?Warn("Received unsolited info ~p", [Info]),
    {noreply, State}.

<commit_msg>fix imem_seco monitoring<commit_after>-module(imem_seco).

-include("imem_seco.hrl").

-define(GET_PASSWORD_LIFE_TIME(__AccountId), ?GET_CONFIG(passwordLifeTime,[__AccountId],100)).
-define(SALT_BYTES, 32).
-define(PWD_HASH, scrypt).                       %% target hash: pwdmd5,md4,md5,sha512,scrypt 
-define(PWD_HASH_LIST, [scrypt,sha512,pwdmd5]).  %% allowed hash types
-define(REQUIRE_PWDMD5, <<"fun(Factors,NetCtx) -> [pwdmd5] -- Factors end">>).  % access | smsott | saml | pwdmd5
-define(AUTH_SMS_TOKEN_RETRY_DELAY, 1000).
-define(FULL_ACCESS, <<"fun(NetCtx) -> true end">>).

-behavior(gen_server).

-record(state, {
        }).

-export([ start_link/1
        ]).

% gen_server interface (monitoring calling processes)
%-export([ monitor/1
%        , cleanup_pid/1
%        ]).

% gen_server behavior callbacks
-export([ init/1
        , handle_call/3
        , handle_cast/2
        , handle_info/2
        , terminate/2
        , code_change/3
        , format_status/2
        ]).

% security context library interface
-export([ drop_seco_tables/1
        , create_credentials/2      % parameters: (Name,Password) or (<PASSWORD>,Password)
        ]).

-export([ authenticate/3            % deprecated
        , auth_start/3
        , auth_add_cred/2
        , auth_abort/1
        , login/1
        , change_credentials/3
        , set_credentials/3
        , set_login_time/2
        , logout/1
        , clone_seco/2
        , account_id/1
        , account_name/1
        ]).

-export([ has_role/3
        , has_permission/3
        ]).

-export([ have_role/2
        , have_permission/2
        ]).


monitor_pid(SKey,Pid) when is_pid(Pid) -> 
    gen_server:call(?MODULE, {monitor,SKey,Pid}).

start_link(Params) ->
    ?Info("~p starting...~n", [?MODULE]),
    case gen_server:start_link({local, ?MODULE}, ?MODULE, Params, [{spawn_opt, [{fullsweep_after, 0}]}]) of
        {ok, _} = Success ->
            ?Info("~p started!~n", [?MODULE]),
            Success;
        Error ->
            ?Error("~p failed to start ~p~n", [?MODULE, Error]),
            Error
    end.

init(_Args) ->
    try %% try creating system tables, may fail if they exist, then check existence 
        if_check_table(none, ddTable),

        ADef = {record_info(fields, ddAccount),?ddAccount,#ddAccount{}},
        catch imem_meta:create_check_table(ddAccount, ADef, [], system),
        imem_meta:create_or_replace_index(ddAccount, name),

        ADDef = {record_info(fields, ddAccountDyn),?ddAccountDyn,#ddAccountDyn{}},
        catch imem_meta:create_check_table(ddAccountDyn, ADDef, [], system),

        RDef = {record_info(fields, ddRole), ?ddRole, #ddRole{}},
        catch imem_meta:create_check_table(ddRole, RDef, [], system),

        SDef = {record_info(fields, ddSeCo), ?ddSeCo, #ddSeCo{}},
        case (catch imem_meta:table_columns(ddSeCo@)) of
            L when L==element(1,SDef) ->    ok;     % field names in table match the new record
            L when is_list(L) ->            ?Info("~p dropping old version of table ddSeCo@~n", [?MODULE]),
                                            imem_meta:drop_table(ddSeCo@);
            _ ->                            ok      % table does not exist
        end,

        catch imem_meta:create_check_table(ddSeCo@, SDef
              , [{scope,local}, {local_content,true},{record_name,ddSeCo}], system),

        PDef = {record_info(fields, ddPerm),?ddPerm, #ddPerm{}},
        catch imem_meta:create_check_table(ddPerm@, PDef
              , [{scope,local}, {local_content,true},{record_name,ddPerm}], system),

        QDef = {record_info(fields, ddQuota), ?ddQuota, #ddQuota{}},
        catch imem_meta:create_check_table(ddQuota@, QDef
              , [{scope,local}, {local_content,true},{record_name,ddQuota}], system),

        case if_select_account_by_name(none, <<"system">>) of
            {[],true} ->  
                    {ok, Pwd} = application:get_env(imem, default_admin_pswd),
                    LocalTime = calendar:local_time(),
                    UserCred=create_credentials(pwdmd5, Pwd),
                    Account = #ddAccount{id=system, type=deamon, name= <<"system">>, credentials=[UserCred]
                                , fullName= <<"DB Administrator">>, lastPasswordChangeTime=LocalTime},
                    AccountDyn = #ddAccountDyn{id=system},
                    if_write(none, ddAccount, Account),                    
                    if_write(none, ddAccountDyn, AccountDyn),                    
                    if_write(none, ddRole, #ddRole{id=system,roles=[],permissions=[manage_system, manage_accounts, manage_system_tables, manage_user_tables]});
            _ ->    ok
        end,
        % imem_meta:fail({"Fail in imem_seco:init on purpose"}),        
        if_truncate_table(none,ddSeCo@),
        if_truncate_table(none,ddPerm@),
        if_truncate_table(none,ddQuota@),

        process_flag(trap_exit, true),
        {ok,#state{}}
    catch
        _Class:Reason -> {stop, {Reason,erlang:get_stacktrace()}} 
    end.

handle_call({monitor, SKey, Pid}, _From, State) ->
    case if_select_seco_keys_by_pid(SKey,Pid) of
        {[],true} ->    % first registration for this pid, need to monitor
            try
                Ref = erlang:monitor(process, Pid),
                ?Info("Monitoring ~p for SKey ~p returns ~p", [Pid, SKey, Ref]),
                {reply, Ref, State}
            catch 
                Class:Reason -> 
                    ?Warn("Monitoring ~p for SKey ~p failed with ~p:~p", [Pid, SKey, Class, Reason]),
                    {reply, {error,Reason}, State}
            end;
        _ ->
            {reply, ok, State}
    end;
handle_call(Request, From, State) ->
    ?Warn("Received unsolited call request ~p from  ~p", [Request,From]),
    {reply, ok, State}.

% handle_cast({stop, Reason}, State) ->
%     {stop,{shutdown,Reason},State};
handle_cast(Request, State) ->
    ?Warn("Received unsolited cast request ~p", [Request]),
    {noreply, State}.

handle_info({'DOWN', _Ref, process, Pid, normal}, State) ->
    ?Info("~p - received normal exit for monitored pid ~p ref ~p~n", [?MODULE, Pid, _Ref]),
    cleanup_pid(Pid),
    {noreply, State};
handle_info({'DOWN', _Ref, process, Pid, _Reason}, State) ->
    ?Info("~p - received unexpected exit for monitored pid ~p ref ~p reason ~p~n", [?MODULE, Pid, _Ref, _Reason]),
    cleanup_pid(Pid),
    {noreply, State};
handle_info(Info, State) ->
    ?Warn("Received unsolited info ~p", [Info]),
    {noreply, State}.


{'fraction_non_alphanumeric': 0.142692750287687, 'fraction_numerical': 0.008131952435749904, 'mean_word_length': 2.4395778364116096, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 71, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1631148', 'n_tokens_mistral': 4394, 'n_tokens_neox': 4199, 'n_words': 844}