This commit is contained in:
Fenris Wolf 2024-11-14 19:57:13 +01:00
parent b5b5dde9e3
commit 54e27f2ad9
11 changed files with 589 additions and 311 deletions

View file

@ -4,7 +4,8 @@
{ {
"kind": "stdout", "kind": "stdout",
"data": { "data": {
"threshold": "info" "threshold": "info",
"format": "human_readable"
} }
} }
], ],

View file

@ -227,6 +227,12 @@ declare namespace lib_plankton.base {
/** /**
*/ */
function object_merge(core: Record<string, any>, mantle: Record<string, any>): Record<string, any>; function object_merge(core: Record<string, any>, mantle: Record<string, any>): Record<string, any>;
/**
*/
function buffer_show(buffer: Buffer, { "block_size": option_block_size, "break_char": option_break_char, }?: {
block_size?: int;
break_char?: string;
}): string;
} }
declare module lib_plankton.pod { declare module lib_plankton.pod {
/** /**
@ -619,6 +625,12 @@ declare namespace lib_plankton.call {
value: (null | type_value); value: (null | type_value);
error: (null | any); error: (null | any);
}; };
/**
*/
function try_catch_wrap_async<type_value>(get_value: (() => Promise<type_value>)): Promise<{
value: (null | type_value);
error: (null | any);
}>;
} }
declare namespace lib_plankton.email { declare namespace lib_plankton.email {
/** /**
@ -3472,7 +3484,6 @@ declare namespace lib_plankton.ical {
} }
declare namespace lib_plankton.http_base { declare namespace lib_plankton.http_base {
/** /**
* @author roydfalk <roydfalk@folksprak.org>
*/ */
type type_request<type_method> = { type type_request<type_method> = {
scheme: ("http" | "https"); scheme: ("http" | "https");
@ -3485,30 +3496,25 @@ declare namespace lib_plankton.http_base {
body: (null | Buffer); body: (null | Buffer);
}; };
/** /**
* @author fenris <frass@greenscale.de>
*/ */
type type_response<type_status_code> = { type type_response<type_status_code> = {
version: (null | string); version: (null | string);
status_code: type_status_code; status_code: type_status_code;
headers: Record<string, string>; headers: Record<string, string>;
body: Buffer; body: (null | Buffer);
}; };
} }
declare namespace lib_plankton.http_base { declare namespace lib_plankton.http_base {
/** /**
* @author fenris <frass@greenscale.de>
*/ */
function encode_request<type_method>(encode_method: ((method: type_method) => string), request: type_request<type_method>): string; function encode_request<type_method>(encode_method: ((method: type_method) => string), request: type_request<type_method>): string;
/** /**
* @author fenris <frass@greenscale.de>
*/ */
function decode_request<type_method, type_request_actual>(decode_method: ((method_raw: string) => type_method), has_body: ((method: type_method) => boolean), request_raw: string): type_request<type_method>; function decode_request<type_method, type_request_actual>(decode_method: ((method_raw: string) => type_method), has_body: ((method: type_method) => boolean), request_raw: string): type_request<type_method>;
/** /**
* @author fenris <frass@greenscale.de>
*/ */
function encode_response<type_status_code>(encode_status_code: ((status_code: type_status_code) => string), get_status_text: ((status_code: type_status_code) => string), response: type_response<type_status_code>): string; function encode_response<type_status_code>(encode_status_code: ((status_code: type_status_code) => string), get_status_text: ((status_code: type_status_code) => string), response: type_response<type_status_code>): string;
/** /**
* @author fenris <frass@greenscale.de>
*/ */
function decode_response<type_status_code>(decode_status_code: ((status_code_raw: string) => type_status_code), response_raw: string): type_response<type_status_code>; function decode_response<type_status_code>(decode_status_code: ((status_code_raw: string) => type_status_code), response_raw: string): type_response<type_status_code>;
/** /**
@ -3642,79 +3648,60 @@ declare namespace lib_plankton.http {
implementation?: ("fetch" | "http_module"); implementation?: ("fetch" | "http_module");
}): Promise<type_response>; }): Promise<type_response>;
} }
/**
* @author fenris
*/
declare namespace lib_plankton.xml { declare namespace lib_plankton.xml {
/** /**
* @author fenris
*/ */
abstract class class_node { abstract class class_node {
/** /**
* @author fenris
*/ */
abstract compile(depth?: int): string; abstract compile(depth?: int): string;
} }
/** /**
* @author fenris
*/ */
class class_node_text extends class_node { class class_node_text extends class_node {
/** /**
* @author fenris
*/ */
protected content: string; protected content: string;
/** /**
* @author fenris
*/ */
constructor(content: string); constructor(content: string);
/** /**
* @author fenris
*/ */
compile(depth?: int): string; compile(depth?: int): string;
} }
/** /**
* @author fenris
*/ */
class class_node_comment extends class_node { class class_node_comment extends class_node {
/** /**
* @author fenris
*/ */
protected content: string; protected content: string;
/** /**
* @author fenris
*/ */
constructor(content: string); constructor(content: string);
/** /**
* @author fenris
*/ */
compile(depth?: int): string; compile(depth?: int): string;
} }
/** /**
* @author fenris
*/ */
class class_node_complex extends class_node { class class_node_complex extends class_node {
/** /**
* @author fenris
*/ */
protected name: string; protected name: string;
/** /**
* @author fenris
*/ */
protected attributes: { protected attributes: {
[key: string]: string; [key: string]: string;
}; };
/** /**
* @author fenris
*/ */
protected children: Array<class_node>; protected children: Array<class_node>;
/** /**
* @author fenris
*/ */
constructor(name: string, attributes?: { constructor(name: string, attributes?: {
[key: string]: string; [key: string]: string;
}, children?: any[]); }, children?: any[]);
/** /**
* @author fenris
*/ */
compile(depth?: int): string; compile(depth?: int): string;
} }
@ -4186,6 +4173,7 @@ declare namespace lib_plankton.rest_base {
}) => Promise<{ }) => Promise<{
status_code: int; status_code: int;
data: type_output; data: type_output;
headers?: (null | Record<string, string>);
}>); }>);
/** /**
*/ */
@ -4209,7 +4197,7 @@ declare namespace lib_plankton.rest_base {
request_body_mimetype: string; request_body_mimetype: string;
request_body_decode: ((http_request_body: Buffer, http_request_header_content_type: (null | string)) => any); request_body_decode: ((http_request_body: Buffer, http_request_header_content_type: (null | string)) => any);
response_body_mimetype: string; response_body_mimetype: string;
response_body_encode: ((output: any) => Buffer); response_body_encode: ((output: any) => (null | Buffer));
}; };
/** /**
*/ */
@ -4316,15 +4304,16 @@ declare namespace lib_plankton.rest_base {
* @todo check request body mimetype? * @todo check request body mimetype?
* @todo check query paramater validity * @todo check query paramater validity
*/ */
function call<type_http_method, type_http_status_code>(encode_http_method: ((http_method: type_http_method) => string), decode_status_code: ((status_code_raw: int) => type_http_status_code), rest: type_rest, http_request: lib_plankton.http_base.type_request<type_http_method>, { "checklevel_restriction": option_checklevel_restriction, "checklevel_input": option_checklevel_input, "checklevel_output": option_checklevel_output, }?: { function call<type_http_method, type_http_status_code>(encode_http_method: ((http_method: type_http_method) => string), is_options_request: ((http_method: type_http_method) => boolean), decode_status_code: ((status_code_raw: int) => type_http_status_code), rest: type_rest, http_request: lib_plankton.http_base.type_request<type_http_method>, { "checklevel_restriction": option_checklevel_restriction, "checklevel_input": option_checklevel_input, "checklevel_output": option_checklevel_output, "set_content_length": option_set_content_length, }?: {
checklevel_restriction?: lib_plankton.api.enum_checklevel; checklevel_restriction?: lib_plankton.api.enum_checklevel;
checklevel_input?: lib_plankton.api.enum_checklevel; checklevel_input?: lib_plankton.api.enum_checklevel;
checklevel_output?: lib_plankton.api.enum_checklevel; checklevel_output?: lib_plankton.api.enum_checklevel;
set_content_length?: boolean;
}): Promise<lib_plankton.http_base.type_response<type_http_status_code>>; }): Promise<lib_plankton.http_base.type_response<type_http_status_code>>;
/** /**
* @see https://swagger.io/specification/#openrest-object * @see https://swagger.io/specification/#openrest-object
*/ */
function to_oas<type_method>(http_request_method_to_oas: ((http_request_method: type_method) => string), has_body: ((method: type_method) => boolean), rest: type_rest, options?: { function to_oas<type_method>(http_request_method_to_oas: ((http_request_method: type_method) => string), has_body: ((method: type_method) => boolean), rest: type_rest, { "version": option_version, "servers": option_servers, }?: {
version?: (null | string); version?: (null | string);
servers?: Array<string>; servers?: Array<string>;
}): any; }): any;
@ -4420,10 +4409,11 @@ declare namespace lib_plankton.rest_caldav {
* @todo check query paramater validity * @todo check query paramater validity
* @todo improve status code mapping * @todo improve status code mapping
*/ */
function call(rest: type_rest, http_request: lib_plankton.caldav.type_request, options?: { function call(rest: type_rest, http_request: lib_plankton.caldav.type_request, { "checklevel_restriction": option_checklevel_restriction, "checklevel_input": option_checklevel_input, "checklevel_output": option_checklevel_output, "set_content_length": option_set_content_length, }?: {
checklevel_restriction?: lib_plankton.api.enum_checklevel; checklevel_restriction?: lib_plankton.api.enum_checklevel;
checklevel_input?: lib_plankton.api.enum_checklevel; checklevel_input?: lib_plankton.api.enum_checklevel;
checklevel_output?: lib_plankton.api.enum_checklevel; checklevel_output?: lib_plankton.api.enum_checklevel;
set_content_length?: boolean;
}): Promise<lib_plankton.caldav.type_response>; }): Promise<lib_plankton.caldav.type_response>;
/** /**
* @see https://swagger.io/specification/#openrest-object * @see https://swagger.io/specification/#openrest-object
@ -4435,13 +4425,11 @@ declare namespace lib_plankton.rest_caldav {
} }
declare namespace lib_plankton.server { declare namespace lib_plankton.server {
/** /**
* @author fenris
*/ */
type type_metadata = { type type_metadata = {
ip_address: string; ip_address: string;
}; };
/** /**
* @author fenris
*/ */
type type_subject = { type type_subject = {
host: string; host: string;
@ -4451,7 +4439,6 @@ declare namespace lib_plankton.server {
serverobj: any; serverobj: any;
}; };
/** /**
* @author fenris
*/ */
function make(handle: ((input: string, metadata?: type_metadata) => Promise<string>), options?: { function make(handle: ((input: string, metadata?: type_metadata) => Promise<string>), options?: {
host?: string; host?: string;
@ -4459,17 +4446,14 @@ declare namespace lib_plankton.server {
threshold?: (null | float); threshold?: (null | float);
}): type_subject; }): type_subject;
/** /**
* @author fenris
* @deprecated * @deprecated
*/ */
function make_old(port: int, handle: ((input: string, metadata?: type_metadata) => Promise<string>)): type_subject; function make_old(port: int, handle: ((input: string, metadata?: type_metadata) => Promise<string>)): type_subject;
/** /**
* @author fenris
* @see https://nodejs.org/api/net.html#serverlistenport-host-backlog-callback * @see https://nodejs.org/api/net.html#serverlistenport-host-backlog-callback
*/ */
function start(subject: type_subject): Promise<void>; function start(subject: type_subject): Promise<void>;
/** /**
* @author fenris
*/ */
function kill(subject: type_subject): void; function kill(subject: type_subject): void;
} }

File diff suppressed because it is too large Load diff

View file

@ -9,8 +9,8 @@ namespace _zeitbild.api
) : void ) : void
{ {
register< register<
any, null,
any string
>( >(
rest_subject, rest_subject,
lib_plankton.caldav.enum_method.propfind, lib_plankton.caldav.enum_method.propfind,
@ -42,56 +42,82 @@ namespace _zeitbild.api
"nullable": false, "nullable": false,
"type": "string", "type": "string",
}), }),
"response_body_mimetype": "application/xml", "response_body_mimetype": "text/xml; charset=utf-8",
"response_body_encode": output => Buffer.from(output), "response_body_encode": output => Buffer.from(output),
// "restriction": restriction_basic_auth,
"restriction": restriction_none, "restriction": restriction_none,
/** /**
* @todo examine body * @todo examine body
*/ */
"execution": async (stuff) => { "execution": async (stuff) => {
return Promise.resolve( const user_id : (null | type_user_id) = await _zeitbild.api.web_auth(
{ stuff.headers["Authorization"]
"status_code": 207, ??
"data": ( stuff.headers["authorization"]
/*"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" ??
+*/ null
lib_plankton.webdav.data_multistatus_encode( );
{ if (user_id === null) {
"responses": [ return Promise.resolve(
{
"status_code": 401,
"data": "",
"extra_headers": {
"WWW-Authenticate": "Basic realm=Restricted",
}
}
);
}
else {
return (
_zeitbild.service.calendar.overview(user_id)
.then(
(data_raw) => Promise.resolve(
data_raw
.map(
(entry) => ({
"id": entry.id,
"name": entry.name,
"access_level": _zeitbild.value_object.access_level.to_string(entry.access_level),
})
)
)
)
.then(
(data) => Promise.resolve({
"status_code": 207,
"data": (
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+
lib_plankton.webdav.data_multistatus_encode(
{ {
"href": "/caldav/events", "responses": [
"body": { {
"propstats": [ "href": "/caldav/events",
{ "body": {
"prop": [ "propstats": data.map(
{"name": "d:displayname", "value": "default"}, (entry) => ({
// {"name": "cs:getctag", "value": "47"}, // TODO correct value "prop": [
// {"name": "current-user-privilege-set", "value": ""}, {"name": "displayname", "value": entry.name},
/* // {"name": "cs:getctag", "value": "47"}, // TODO correct value
"uid", // {"name": "current-user-privilege-set", "value": ""},
"dtstamp", ],
"dtstart", "status": "HTTP/2.0 200 OK",
"dtend", "description": entry.access_level,
"summary", })
"description", ),
"url",
"location",
*/
],
"status": "HTTP/2.0 200 OK",
"description": null,
}, },
] "description": null,
}, }
],
"description": null, "description": null,
} }
], )
"description": null, ),
} })
) )
), );
} }
);
} }
} }
); );

View file

@ -0,0 +1,46 @@
namespace _zeitbild.api
{
/**
*/
export function register_caldav_sniff(
rest_subject : lib_plankton.rest_caldav.type_rest
) : void
{
register<
any,
null
>(
rest_subject,
lib_plankton.caldav.enum_method.options,
"/caldav",
{
"restriction": restriction_none,
"execution": async (stuff) => {
const permitted : boolean = await restriction_basic_auth(stuff);
if (! permitted) {
return Promise.resolve(
{
"status_code": 401,
"data": null,
"extra_headers": {
"WWW-Authenticate": "Basic realm=Restricted",
}
}
);
}
else {
return Promise.resolve(
{
"status_code": 200,
"data": null
}
);
}
}
}
);
}
}

View file

@ -25,6 +25,77 @@ namespace _zeitbild.api
} }
/**
* @todo outsource?
*/
export async function web_auth(
authorization_string : (null | string)
) : Promise<(null | _zeitbild.type_user_id)>
{
if (authorization_string === null) {
return Promise.resolve<(null | _zeitbild.type_user_id)>(null);
}
else {
const parts : Array<string> = authorization_string.split(" ");
const strategy : string = parts[0];
const data_raw : string = parts.slice(1).join(" ");
switch (strategy) {
default: {
lib_plankton.log.notice(
"zeitbild.web_auth.unhandled_strategy",
{
"strategy": strategy,
}
);
return Promise.resolve<(null | _zeitbild.type_user_id)>(null);
break;
}
case "Basic": {
const data_raw_decoded : string = lib_plankton.base64.decode(data_raw);
const parts_ : Array<string> = data_raw_decoded.split(":");
const username : string = parts_[0];
const password_is : string = parts_.slice(1).join(":");
const {"value": user_id, "error": error} = await lib_plankton.call.try_catch_wrap_async<_zeitbild.type_user_id>(
() => _zeitbild.service.user.identify(username)
);
if (error !== null) {
lib_plankton.log.notice(
"zeitbild.web_auth.unknown_user",
{
"username": username,
}
);
return Promise.resolve<(null | _zeitbild.type_user_id)>(null);
}
else {
const password_shall : string = lib_plankton.sha256.get(
username,
_zeitbild.conf.get()["misc"]["auth_salt"]
);
if (! (password_is === password_shall)) {
/**
* @todo remove
*/
lib_plankton.log.notice(
"zeitbild.web_auth.wrong_pasword",
{
"shall": password_shall,
"is": password_is,
}
);
return Promise.resolve<(null | _zeitbild.type_user_id)>(null);
}
else {
return Promise.resolve<(null | _zeitbild.type_user_id)>(user_id);
}
}
break;
}
}
}
}
/** /**
*/ */
export const restriction_logged_in : lib_plankton.rest_caldav.type_restriction<any> = ( export const restriction_logged_in : lib_plankton.rest_caldav.type_restriction<any> = (
@ -36,6 +107,26 @@ namespace _zeitbild.api
); );
/**
*/
export const restriction_basic_auth : lib_plankton.rest_caldav.type_restriction<any> = (
(stuff) => (
web_auth(
stuff.headers["Authorization"]
??
stuff.headers["authorization"]
??
null
)
.then<boolean>(
(user_id) => Promise.resolve<boolean>(
(user_id !== null)
)
)
)
);
/** /**
*/ */
export const restriction_none : lib_plankton.rest_caldav.type_restriction<any> = ( export const restriction_none : lib_plankton.rest_caldav.type_restriction<any> = (

View file

@ -52,6 +52,7 @@ namespace _zeitbild.api
} }
// caldav // caldav
{ {
_zeitbild.api.register_caldav_sniff(rest_subject);
_zeitbild.api.register_caldav_probe(rest_subject); _zeitbild.api.register_caldav_probe(rest_subject);
_zeitbild.api.register_caldav_get(rest_subject); _zeitbild.api.register_caldav_get(rest_subject);
} }

View file

@ -41,7 +41,17 @@ namespace _zeitbild.conf
"error" "error"
], ],
"default": "info" "default": "info"
} },
"format": {
"nullable": false,
"type": "string",
"enum": [
"human_readable",
"jsonl",
"jsonl_structured",
],
"default": "human_readable",
},
}, },
"required": [ "required": [
] ]

View file

@ -273,11 +273,31 @@ async function main(
"kind": "std", "kind": "std",
"data": { "data": {
"target": "stdout", "target": "stdout",
"format": { "format": lib_plankton.call.distinguish(
"kind": "human_readable", {
"data": { "kind": log_output.data.format,
"data": null,
},
{
"jsonl": () => ({
"kind": "jsonl",
"data": {
"structured": false,
}
}),
"jsonl_structured": () => ({
"kind": "jsonl",
"data": {
"structured": true,
}
}),
"human_readable": () => ({
"kind": "human_readable",
"data": {
}
}),
} }
} ),
} }
}, },
"threshold": log_output.data.threshold, "threshold": log_output.data.threshold,
@ -419,6 +439,7 @@ async function main(
"checklevel_restriction": lib_plankton.api.enum_checklevel.hard, "checklevel_restriction": lib_plankton.api.enum_checklevel.hard,
// "checklevel_input": lib_plankton.api.enum_checklevel.soft, // "checklevel_input": lib_plankton.api.enum_checklevel.soft,
// "checklevel_output": lib_plankton.api.enum_checklevel.soft, // "checklevel_output": lib_plankton.api.enum_checklevel.soft,
"set_content_length": false,
} }
); );
const output : string = lib_plankton.caldav.encode_response(http_response); const output : string = lib_plankton.caldav.encode_response(http_response);
@ -448,6 +469,7 @@ async function main(
) )
.catch( .catch(
(error) => { (error) => {
// console.error(error);
process.stderr.write(String(error) + "\n"); process.stderr.write(String(error) + "\n");
} }
) )

View file

@ -348,7 +348,13 @@ namespace _zeitbild.service.calendar
{ {
} }
); );
const ics_raw : string = http_response.body.toString(); const ics_raw : string = (
(http_response.body === null)
?
""
:
http_response.body.toString()
);
const vcalendar_list : Array<lib_plankton.ical.type_vcalendar> = lib_plankton.ical.ics_decode_multi( const vcalendar_list : Array<lib_plankton.ical.type_vcalendar> = lib_plankton.ical.ics_decode_multi(
ics_raw, ics_raw,
{ {

View file

@ -76,6 +76,7 @@ ${dir_temp}/zeitbild-unlinked.js: \
${dir_source}/api/actions/calendar_event_remove.ts \ ${dir_source}/api/actions/calendar_event_remove.ts \
${dir_source}/api/actions/events.ts \ ${dir_source}/api/actions/events.ts \
${dir_source}/api/actions/export_ical.ts \ ${dir_source}/api/actions/export_ical.ts \
${dir_source}/api/actions/caldav_sniff.ts \
${dir_source}/api/actions/caldav_probe.ts \ ${dir_source}/api/actions/caldav_probe.ts \
${dir_source}/api/actions/caldav_get.ts \ ${dir_source}/api/actions/caldav_get.ts \
${dir_source}/api/functions.ts \ ${dir_source}/api/functions.ts \