Schema validation
parent
c6c547df51
commit
b6735ee54d
|
@ -3,13 +3,14 @@
|
||||||
*.beam
|
*.beam
|
||||||
*.o
|
*.o
|
||||||
*.plt
|
*.plt
|
||||||
|
*.lock
|
||||||
|
|
||||||
# Dipendenze scaricate
|
# Dipendenze scaricate
|
||||||
/deps/
|
/deps/
|
||||||
|
|
||||||
# File di test e log
|
# File di test e log
|
||||||
.eunit/
|
.eunit/
|
||||||
logs/
|
log/
|
||||||
erl_crash.dump
|
erl_crash.dump
|
||||||
|
|
||||||
# File generati da strumenti
|
# File generati da strumenti
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"title": "ActivityPub Note",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["type", "id", "content"],
|
||||||
|
"properties": {
|
||||||
|
"@context": { "type": "string" },
|
||||||
|
"type": { "type": "string", "enum": ["Note"] },
|
||||||
|
"id": { "type": "string" },
|
||||||
|
"attributedTo": { "type": "string" },
|
||||||
|
"content": { "type": "string" },
|
||||||
|
"to": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
{deps, [
|
{deps, [
|
||||||
|
jsonlog,
|
||||||
|
jesse,
|
||||||
{cowboy, "2.10.0"},
|
{cowboy, "2.10.0"},
|
||||||
{jsx, "3.1.0"}
|
{jsx, "3.1.0"}
|
||||||
]}.
|
]}.
|
||||||
|
|
17
rebar.lock
17
rebar.lock
|
@ -1,17 +0,0 @@
|
||||||
{"1.2.0",
|
|
||||||
[{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.10.0">>},0},
|
|
||||||
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.12.1">>},1},
|
|
||||||
{<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},0},
|
|
||||||
{<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},1}]}.
|
|
||||||
[
|
|
||||||
{pkg_hash,[
|
|
||||||
{<<"cowboy">>, <<"FF9FFEFF91DAE4AE270DD975642997AFE2A1179D94B1887863E43F681A203E26">>},
|
|
||||||
{<<"cowlib">>, <<"A9FA9A625F1D2025FE6B462CB865881329B5CAFF8F1854D1CBC9F9533F00E1E1">>},
|
|
||||||
{<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>},
|
|
||||||
{<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>}]},
|
|
||||||
{pkg_hash_ext,[
|
|
||||||
{<<"cowboy">>, <<"3AFDCCB7183CC6F143CB14D3CF51FA00E53DB9EC80CDCD525482F5E99BC41D6B">>},
|
|
||||||
{<<"cowlib">>, <<"163B73F6367A7341B33C794C4E88E7DBFE6498AC42DCD69EF44C5BC5507C8DB0">>},
|
|
||||||
{<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>},
|
|
||||||
{<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>}]}
|
|
||||||
].
|
|
|
@ -10,6 +10,7 @@
|
||||||
start(_Type, _Args) ->
|
start(_Type, _Args) ->
|
||||||
|
|
||||||
%% creiamo un utente di test, solo per vedere se mnesia va
|
%% creiamo un utente di test, solo per vedere se mnesia va
|
||||||
|
log_handler:init(), %% va per primo, se no, non logga l'avvio
|
||||||
db_organization:setup(),
|
db_organization:setup(),
|
||||||
db_organization:init(),
|
db_organization:init(),
|
||||||
users_local_check:check_ENV(), %% controlla le variabili d'ambiente
|
users_local_check:check_ENV(), %% controlla le variabili d'ambiente
|
||||||
|
|
|
@ -26,7 +26,7 @@ remove_oldest(Tab, Fraction) ->
|
||||||
lists:foreach(fun(Rec) -> mnesia:dirty_delete_object(Rec) end, ToDelete).
|
lists:foreach(fun(Rec) -> mnesia:dirty_delete_object(Rec) end, ToDelete).
|
||||||
|
|
||||||
make_pattern(global_message) -> #global_message{id='_', activity='_', timestamp='_'};
|
make_pattern(global_message) -> #global_message{id='_', activity='_', timestamp='_'};
|
||||||
make_pattern(ap_users) -> #ap_users{id='_', name='_', email='_', created_at='_'}. %% <-- PUNTO QUI DIOCANE!
|
make_pattern(ap_users) -> #ap_users{id='_', username='_', email='_', created_at='_'}. %% <-- PUNTO QUI DIOCANE!
|
||||||
|
|
||||||
get_timestamp(#global_message{timestamp=T}) -> T;
|
get_timestamp(#global_message{timestamp=T}) -> T;
|
||||||
get_timestamp(#ap_users{created_at=T}) -> T. %% <-- PUNTO SOLO ALLA FINE, DIOCANE!
|
get_timestamp(#ap_users{created_at=T}) -> T. %% <-- PUNTO SOLO ALLA FINE, DIOCANE!
|
||||||
|
|
|
@ -1,40 +1,66 @@
|
||||||
%% index_handler.erl
|
%% index_handler.erl
|
||||||
-module(index_handler).
|
-module(index_handler).
|
||||||
-behaviour(cowboy_handler).
|
-behaviour(cowboy_handler).
|
||||||
|
-include("db_safe_insert.hrl").
|
||||||
-export([init/2]).
|
-export([init/2]).
|
||||||
|
|
||||||
init(Req, State) ->
|
init(Req, State) ->
|
||||||
{ok, Body, Req2} = cowboy_req:read_body(Req),
|
{Body, Req2} = read_full_body(Req),
|
||||||
Activity = jsx:decode(Body, [return_maps]),
|
case json_validate:validate_activity(Body) of
|
||||||
|
ok ->
|
||||||
%% Validazione dei campi obbligatori
|
%% Decodifica il JSON già validato
|
||||||
case validate_activity(Activity) of
|
Activity = jsx:decode(Body, [return_maps]),
|
||||||
false ->
|
|
||||||
{ok, Resp} = cowboy_req:reply(400, #{}, <<"Invalid ActivityPub message">>, Req2),
|
|
||||||
{ok, Resp, State};
|
|
||||||
true ->
|
|
||||||
To = maps:get(<<"to">>, Activity, []),
|
To = maps:get(<<"to">>, Activity, []),
|
||||||
%% Qui uso la funzione del nuovo modulo!
|
|
||||||
case users_local_check:has_local_recipient(To) of
|
case users_local_check:has_local_recipient(To) of
|
||||||
true ->
|
true ->
|
||||||
{ok, Resp} = cowboy_req:reply(200, #{}, <<"Delivered to local user">>, Req2),
|
cowboy_req:reply(200, #{}, <<"Delivered to local user">>, Req2),
|
||||||
{ok, Resp, State};
|
{ok, Req2, State};
|
||||||
false ->
|
false ->
|
||||||
%% Salva nell'inbox globale
|
%% SAFE INSERT NELLA GLOBAL INBOX
|
||||||
timeline_db:add_message(Activity),
|
InsertResult = db_safe_insert:safe_insert(global_message, #global_message{
|
||||||
{ok, Resp} = cowboy_req:reply(202, #{}, <<"Saved to global inbox">>, Req2),
|
id = make_ref(),
|
||||||
{ok, Resp, State}
|
activity = Activity,
|
||||||
end
|
timestamp = erlang:system_time(second)
|
||||||
|
}),
|
||||||
|
case InsertResult of
|
||||||
|
ok ->
|
||||||
|
cowboy_req:reply(202, #{}, <<"Saved to global inbox">>, Req2),
|
||||||
|
{ok, Req2, State};
|
||||||
|
{aborted, Reason} ->
|
||||||
|
logger:error("DB aborted: ~p", [Reason]),
|
||||||
|
cowboy_req:reply(500, #{}, <<"Database error">>, Req2),
|
||||||
|
{ok, Req2, State};
|
||||||
|
Other ->
|
||||||
|
logger:error("DB unknown error: ~p", [Other]),
|
||||||
|
cowboy_req:reply(500, #{}, <<"Unknown DB error">>, Req2),
|
||||||
|
{ok, Req2, State}
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
{error, malformed_json} ->
|
||||||
|
cowboy_req:reply(400, #{}, <<"Malformed JSON">>, Req2),
|
||||||
|
{ok, Req2, State};
|
||||||
|
{error, missing_type} ->
|
||||||
|
cowboy_req:reply(400, #{}, <<"Missing 'type' field">>, Req2),
|
||||||
|
{ok, Req2, State};
|
||||||
|
{error, {unsupported_type, TypeBin}} ->
|
||||||
|
Msg = <<"Unsupported type: ", TypeBin/binary>>,
|
||||||
|
cowboy_req:reply(400, #{}, Msg, Req2),
|
||||||
|
{ok, Req2, State};
|
||||||
|
{error, invalid_type_field} ->
|
||||||
|
cowboy_req:reply(400, #{}, <<"Invalid 'type' field">>, Req2),
|
||||||
|
{ok, Req2, State};
|
||||||
|
{error, Errors} ->
|
||||||
|
ErrorMsg = io_lib:format("Invalid ActivityPub message: ~p", [Errors]),
|
||||||
|
cowboy_req:reply(400, #{}, list_to_binary(ErrorMsg), Req2),
|
||||||
|
{ok, Req2, State}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
%% Funzione di utilità per leggere tutto il body
|
||||||
%% vediamo di controllare se ci sono i campi obbligatori minimi di ActivityPub:
|
read_full_body(Req) ->
|
||||||
|
case cowboy_req:read_body(Req) of
|
||||||
validate_activity(Activity) when is_map(Activity) ->
|
{ok, Body, Req2} ->
|
||||||
maps:is_key(<<"type">>, Activity) andalso
|
{Body, Req2};
|
||||||
maps:is_key(<<"to">>, Activity) andalso
|
{more, Data, Req2} ->
|
||||||
maps:is_key(<<"actor">>, Activity) andalso
|
{Rest, FinalReq} = read_full_body(Req2),
|
||||||
maps:is_key(<<"object">>, Activity) andalso
|
{<<Data/binary, Rest/binary>>, FinalReq}
|
||||||
maps:is_key(<<"content">>, Activity).
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
-module(json_validate).
|
||||||
|
-export([validate_activity/1, load_schema/1]).
|
||||||
|
|
||||||
|
load_schema(SchemaName) ->
|
||||||
|
Filename = "priv/" ++ SchemaName ++ ".json",
|
||||||
|
{ok, Bin} = file:read_file(Filename),
|
||||||
|
jsx:decode(Bin, [return_maps]).
|
||||||
|
|
||||||
|
validate_activity(JsonBin) ->
|
||||||
|
case catch jsx:decode(JsonBin, [return_maps]) of
|
||||||
|
{'EXIT', _} ->
|
||||||
|
{error, malformed_json};
|
||||||
|
JsonMap when is_map(JsonMap) ->
|
||||||
|
case maps:get(<<"type">>, JsonMap, undefined) of
|
||||||
|
undefined ->
|
||||||
|
{error, missing_type};
|
||||||
|
TypeBin when is_binary(TypeBin) ->
|
||||||
|
TypeLower = string:lowercase(binary_to_list(TypeBin)),
|
||||||
|
case TypeLower of
|
||||||
|
"note" ->
|
||||||
|
Schema = load_schema("note"),
|
||||||
|
case jesse:validate_with_schema(Schema, JsonMap) of
|
||||||
|
{ok, _} -> ok;
|
||||||
|
{error, Errors} -> {error, Errors}
|
||||||
|
end;
|
||||||
|
OtherType ->
|
||||||
|
{error, {unsupported_type, TypeBin}}
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
{error, invalid_type_field}
|
||||||
|
end
|
||||||
|
end.
|
|
@ -0,0 +1,36 @@
|
||||||
|
-module(log_handler).
|
||||||
|
-export([init/0, info/2, error/2, warning/2, debug/2, log_map/2]).
|
||||||
|
|
||||||
|
init() ->
|
||||||
|
ok = ensure_log_dir(),
|
||||||
|
lists:foreach(fun(H) -> logger:remove_handler(H) end, logger:get_handler_ids()),
|
||||||
|
logger:add_handler(file, logger_std_h, #{
|
||||||
|
level => debug,
|
||||||
|
config => #{file => "log/erlang.json", sync_mode_qlen => 0},
|
||||||
|
formatter => {logger_formatter, #{}}
|
||||||
|
}),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
|
||||||
|
info(Format, Args) ->
|
||||||
|
logger:info(Format, Args).
|
||||||
|
|
||||||
|
error(Format, Args) ->
|
||||||
|
logger:error(Format, Args).
|
||||||
|
|
||||||
|
warning(Format, Args) ->
|
||||||
|
logger:warning(Format, Args).
|
||||||
|
|
||||||
|
debug(Format, Args) ->
|
||||||
|
logger:debug(Format, Args).
|
||||||
|
|
||||||
|
%% Logga direttamente una mappa come evento JSON
|
||||||
|
log_map(Level, Map) when is_map(Map) ->
|
||||||
|
logger:log(Level, Map).
|
||||||
|
|
||||||
|
%% Utility per assicurarsi che la directory log/ esista
|
||||||
|
ensure_log_dir() ->
|
||||||
|
case file:read_file_info("log") of
|
||||||
|
{ok, _} -> ok;
|
||||||
|
_ -> file:make_dir("log")
|
||||||
|
end.
|
|
@ -5,7 +5,7 @@
|
||||||
add_user(Name, Email) ->
|
add_user(Name, Email) ->
|
||||||
Id = erlang:unique_integer([monotonic, positive]),
|
Id = erlang:unique_integer([monotonic, positive]),
|
||||||
Timestamp = erlang:system_time(microsecond),
|
Timestamp = erlang:system_time(microsecond),
|
||||||
Record = #ap_users{id=Id, name=Name, email=Email, created_at=Timestamp},
|
Record = #ap_users{id=Id, username=Name, email=Email, created_at=Timestamp},
|
||||||
db_safe_insert:safe_insert(ap_users, Record).
|
db_safe_insert:safe_insert(ap_users, Record).
|
||||||
|
|
||||||
get_user(Id) ->
|
get_user(Id) ->
|
||||||
|
@ -20,7 +20,7 @@ get_user(Id) ->
|
||||||
|
|
||||||
all_users() ->
|
all_users() ->
|
||||||
F = fun() ->
|
F = fun() ->
|
||||||
mnesia:match_object(#ap_users{id='_', name='_', email='_', created_at='_'})
|
mnesia:match_object(#ap_users{id='_', username='_', email='_', created_at='_'})
|
||||||
end,
|
end,
|
||||||
{atomic, Users} = mnesia:transaction(F),
|
{atomic, Users} = mnesia:transaction(F),
|
||||||
Users.
|
Users.
|
||||||
|
|
|
@ -12,9 +12,10 @@
|
||||||
check_ENV() ->
|
check_ENV() ->
|
||||||
case os:getenv("AP_FQDN") of
|
case os:getenv("AP_FQDN") of
|
||||||
false ->
|
false ->
|
||||||
io:format("Errore: la variabile di ambiente AP_FQDN non è settata!~n"),
|
log_handler:error("Errore: la variabile di ambiente AP_FQDN non è settata!~n"),
|
||||||
erlang:halt(1);
|
erlang:halt(1);
|
||||||
_ ->
|
_ ->
|
||||||
|
log_handler:debug("La variabile AP_FQDN esiste, bene", []),
|
||||||
ok
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -24,10 +25,10 @@ get_fqdn() ->
|
||||||
logger:notice("Chiamata get_fqdn()~n"),
|
logger:notice("Chiamata get_fqdn()~n"),
|
||||||
case os:getenv("AP_FQDN") of
|
case os:getenv("AP_FQDN") of
|
||||||
false ->
|
false ->
|
||||||
logger:notice("Errore: la variabile di ambiente AP_FQDN deve essere settata!~n"),
|
log_handler:error("Errore: la variabile di ambiente AP_FQDN deve essere settata!~n"),
|
||||||
exit({error, ap_fqdn_not_set});
|
exit({error, ap_fqdn_not_set});
|
||||||
FQDN ->
|
FQDN ->
|
||||||
logger:notice("FQDN settata correttamente: ~p", [FQDN]),
|
log_handler:debug("FQDN settata correttamente: ~p", [FQDN]),
|
||||||
list_to_binary(FQDN)
|
list_to_binary(FQDN)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue