<commit_before>%
% Copyright (c) 2016-2018 <NAME> <<EMAIL>>
% All rights reserved.
% Distributed under the terms of the MIT License. See the LICENSE file.
%
-module(lorawan_admin).

-export([handle_authorization/2]).
-export([check_health/1, check_health/3, parse/1, build/1]).
-export([parse_field/2, build_field/2]).
-export([timestamp_to_json_date/1]).

-export([check_alive/1, check_dwell/1]).
-export([check_reset/1, check_battery/1, check_margin/1, check_adr/1, check_rxwin/1]).

-include("lorawan.hrl").
-include("lorawan_db.hrl").

handle_authorization(Req, State) ->
    case cowboy_req:parse_header(<<"authorization">>, Req) of
        {digest, Params} ->
            Method = cowboy_req:method(Req),
            UserName = proplists:get_value(<<"username">>, Params, <<>>),
            Nonce = proplists:get_value(<<"nonce">>, Params, <<>>),
            URI = proplists:get_value(<<"uri">>, Params, <<>>),
            Response = proplists:get_value(<<"response">>, Params, <<>>),
            % retrieve and check password
            case get_password_hash(<<"admin">>, UserName) of
                undefined ->
                    {{false, digest_header()}, Req, State};
                HA1 ->
                    case lorawan_http_digest:response(Method, URI, <<>>, HA1, Nonce) of
                        Response ->
                            {true, Req, State};
                        _Else ->
                            {{false, digest_header()}, Req, State}
                    end
            end;
        _Else ->
            {{false, digest_header()}, Req, State}
    end.

digest_header() ->
    Nonce = lorawan_http_digest:nonce(16),
    lorawan_http_digest:header(digest, [
        {<<"realm">>, ?REALM}, {<<"nonce">>, Nonce}]).

get_password_hash(Role, UserName) ->
    case mnesia:dirty_read(users, UserName) of
        [#user{pass_ha1=Hash, roles=undefined}] ->
            % temporary provisions for backward compatibility
            Hash;
        [#user{pass_ha1=Hash, roles=Roles}] ->
            case lists:member(Role, Roles) of
                true ->
                    Hash;
                false ->
                    undefined
            end;
        _Else ->
            undefined
    end.

check_health(#gateway{} = Gateway) ->
    check_health(Gateway, ?MODULE, [check_alive, check_dwell]);
check_health(#node{} = Node) ->
    check_health(Node, ?MODULE, [check_reset, check_battery, check_margin, check_adr, check_rxwin]);
check_health(_Other) ->
    undefined.

check_health(Rec, Module, Funs) ->
    lists:foldl(
        fun
            (_Fun, undefined) ->
                undefined;
            (Fun, {Decay, Alerts}) ->
                case apply(Module, Fun, [Rec]) of
                    {DecayInc, AlertInc} ->
                        {Decay + DecayInc, [AlertInc | Alerts]};
                    ok ->
                        {Decay, Alerts};
                    undefined ->
                        undefined
                end
        end,
        {0, []}, Funs).

check_alive(#gateway{last_alive=undefined}) ->
    {100, disconnected};
check_alive(#gateway{last_alive=LastAlive}) ->
    case calendar:datetime_to_gregorian_seconds(calendar:universal_time()) -
            calendar:datetime_to_gregorian_seconds(LastAlive) of
        Silent when Silent > 20 ->
            {Silent div 20, disconnected};
        _Else ->
            ok
    end.

check_dwell(#gateway{dwell=Dwell}) when is_list(Dwell) ->
    MaxSum =
        lists:foldl(
            fun({_, {_, _, Sum}}, Max) -> max(Sum, Max) end,
            0, Dwell),
    Percent = MaxSum/36000,
    if
        Percent > 1 ->
            {trunc(10*Percent), dwell_time_violated};
        true ->
            ok<commit_msg>Issue #346: Reduce 'Data has changed'<commit_after>%
% Copyright (c) 2016-2018 <NAME> <<EMAIL>>
% All rights reserved.
% Distributed under the terms of the MIT License. See the LICENSE file.
%
-module(lorawan_admin).

-export([handle_authorization/2]).
-export([check_health/1, check_health/3, parse/1, build/1]).
-export([parse_field/2, build_field/2]).
-export([timestamp_to_json_date/1]).

-export([check_alive/1, check_dwell/1]).
-export([check_reset/1, check_battery/1, check_margin/1, check_adr/1, check_rxwin/1]).

-include("lorawan.hrl").
-include("lorawan_db.hrl").

handle_authorization(Req, State) ->
    case cowboy_req:parse_header(<<"authorization">>, Req) of
        {digest, Params} ->
            Method = cowboy_req:method(Req),
            UserName = proplists:get_value(<<"username">>, Params, <<>>),
            Nonce = proplists:get_value(<<"nonce">>, Params, <<>>),
            URI = proplists:get_value(<<"uri">>, Params, <<>>),
            Response = proplists:get_value(<<"response">>, Params, <<>>),
            % retrieve and check password
            case get_password_hash(<<"admin">>, UserName) of
                undefined ->
                    {{false, digest_header()}, Req, State};
                HA1 ->
                    case lorawan_http_digest:response(Method, URI, <<>>, HA1, Nonce) of
                        Response ->
                            {true, Req, State};
                        _Else ->
                            {{false, digest_header()}, Req, State}
                    end
            end;
        _Else ->
            {{false, digest_header()}, Req, State}
    end.

digest_header() ->
    Nonce = lorawan_http_digest:nonce(16),
    lorawan_http_digest:header(digest, [
        {<<"realm">>, ?REALM}, {<<"nonce">>, Nonce}]).

get_password_hash(Role, UserName) ->
    case mnesia:dirty_read(users, UserName) of
        [#user{pass_ha1=Hash, roles=undefined}] ->
            % temporary provisions for backward compatibility
            Hash;
        [#user{pass_ha1=Hash, roles=Roles}] ->
            case lists:member(Role, Roles) of
                true ->
                    Hash;
                false ->
                    undefined
            end;
        _Else ->
            undefined
    end.

check_health(#gateway{} = Gateway) ->
    check_health(Gateway, ?MODULE, [check_alive, check_dwell]);
check_health(#node{} = Node) ->
    check_health(Node, ?MODULE, [check_reset, check_battery, check_margin, check_adr, check_rxwin]);
check_health(_Other) ->
    undefined.

check_health(Rec, Module, Funs) ->
    lists:foldl(
        fun
            (_Fun, undefined) ->
                undefined;
            (Fun, {Decay, Alerts}) ->
                case apply(Module, Fun, [Rec]) of
                    {DecayInc, AlertInc} ->
                        {Decay + DecayInc, [AlertInc | Alerts]};
                    ok ->
                        {Decay, Alerts};
                    undefined ->
                        undefined
                end
        end,
        {0, []}, Funs).

check_alive(#gateway{last_alive=undefined}) ->
    {100, disconnected};
check_alive(#gateway{last_alive=LastAlive}) ->
    case calendar:datetime_to_gregorian_seconds(calendar:universal_time()) -
            calendar:datetime_to_gregorian_seconds(LastAlive) of
        Silent when Silent > 20 ->
            {min(Silent div 20, 100), disconnected};
        _Else ->
            ok
    end.

check_dwell(#gateway{dwell=Dwell}) when is_list(Dwell) ->
    MaxSum =
        lists:foldl(
            fun({_, {_, _, Sum}}, Max) -> max(Sum, Max) end,
            0, Dwell),
    Percent = MaxSum/36000,
    if
        Percent > 1 ->
            {trunc(10*Percent), dwell_time_violated};
        true ->
            ok
{'fraction_non_alphanumeric': 0.14062916555585178, 'fraction_numerical': 0.013063183151159691, 'mean_word_length': 1.9435072577481365, 'pattern_counts': {'":': 0, '<': 61, '<?xml version=': 0, '>': 127, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '731410', 'n_tokens_mistral': 2425, 'n_tokens_neox': 2272, 'n_words': 451}