From bef69283f62bfdd53fda30399122ea5e0eb74ec1 Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Thu, 23 Oct 2025 19:08:00 +0200 Subject: [PATCH] [task-416] [mod] repository:calendar:sonder-query los werden --- lib/plankton/plankton.d.ts | 2 +- source/api/actions/session_oidc.ts | 179 +++----------- source/auth.ts | 158 ++++++++++-- source/repositories/calendar.ts | 226 ++++++++++-------- source/repositories/sql/calendar_overview.sql | 15 -- tools/makefile | 9 +- 6 files changed, 299 insertions(+), 290 deletions(-) delete mode 100644 source/repositories/sql/calendar_overview.sql diff --git a/lib/plankton/plankton.d.ts b/lib/plankton/plankton.d.ts index d07a527..8b1db32 100644 --- a/lib/plankton/plankton.d.ts +++ b/lib/plankton/plankton.d.ts @@ -4597,7 +4597,7 @@ declare namespace lib_plankton.auth.oidc { type type_token = string; /** */ - type type_userinfo = { + export type type_userinfo = { name: (null | string); label: (null | string); email: (null | string); diff --git a/source/api/actions/session_oidc.ts b/source/api/actions/session_oidc.ts index fee9dda..ebf7515 100644 --- a/source/api/actions/session_oidc.ts +++ b/source/api/actions/session_oidc.ts @@ -21,38 +21,6 @@ along with »zeitbild«. If not, see . namespace _zeitbild.api { - /** - */ - function get_group_name( - group_name_raw : string - ) - : string - { - return lib_plankton.string.coin( - "auto-{{name_raw}}", - { - "name_raw": group_name_raw, - } - ); - } - - - /** - */ - function get_group_label( - group_name_raw : string - ) - : string - { - return lib_plankton.string.coin( - "{{name_raw}}", - { - "name_raw": group_name_raw, - } - ); - } - - /** */ export function register_session_oidc( @@ -103,132 +71,39 @@ namespace _zeitbild.api "execution": async (stuff) => { const data : { token : string; - userinfo : { - name : (null | string); - email : (null | string); - groups : (null | Array); - }; + userinfo : lib_plankton.auth.oidc.type_userinfo; redirect_uri_template : string; } = await _zeitbild.auth.oidc_handle_authorization_callback( (stuff.headers["Cookie"] ?? stuff.headers["cookie"] ?? null), stuff.query_parameters ); - if (data.userinfo.name === null) - { - return Promise.reject( - new Error( - "IDP did not return user name" - ) - ); - } - else - { - // groups - const group_ids : Array<_zeitbild.type_group_id> = await Promise.all<_zeitbild.type_group_id>( - (data.userinfo.groups ?? []) - .map( - async (group_name_raw) => { - const group_name : string = get_group_name(group_name_raw); - const group_id_raw : (null | _zeitbild.type_group_id) = await ( - _zeitbild.repository.group.identify(group_name) - .catch(() => Promise.resolve(null)) - ); - if (group_id_raw === null) - { - const group_id : _zeitbild.type_group_id = await _zeitbild.service.group.add( - { - "name": group_name, - "label": get_group_label(group_name_raw), - } - ); - return group_id; - } - else - { - const group_id : _zeitbild.type_group_id = group_id_raw; - await _zeitbild.service.group.change( - group_id, - { - "name": group_name, - "label": get_group_label(group_name_raw), - } - ); - return group_id; - } + + const user = await _zeitbild.auth.oidc_adapt_user(data.userinfo); + + const session_key : string = await lib_plankton.session.begin( + user.object.name, + { + "data": { + "oidc_token": data.token, + } + } + ); + return Promise.resolve( + { + "status_code": 200, + "data": lib_plankton.string.coin( + "", + { + "url": lib_plankton.string.coin( + data.redirect_uri_template, + { + "session_key": session_key, + } + ), } - ) - ); - - const user_id : _zeitbild.type_user_id = await (async () => { - const user_object : _zeitbild.type_user_object = { - "name": (data.userinfo.name as string), - "groups": group_ids, - "email_address": data.userinfo.email, - "dav_token": null, - }; - const user_id_raw : (null | _zeitbild.type_user_id) = await ( - _zeitbild.service.user.identify(data.userinfo.name as string) - .catch(() => Promise.resolve(null)) - ); - if (user_id_raw === null) - { - // provision - const user_id : _zeitbild.type_user_id = await _zeitbild.service.user.add( - user_object - ); - lib_plankton.log.info( - "user_provisioned", - { - "id": user_id, - "name": user_object.name, - } - ); - return user_id; - } - else - { - const user_id : _zeitbild.type_user_id = user_id_raw; - // update - await _zeitbild.service.user.change( - user_id, - user_object - ); - lib_plankton.log.info( - "user_updated", - { - "id": user_id, - "name": user_object.name, - } - ); - return user_id; - } - }) (); - - const session_key : string = await lib_plankton.session.begin( - data.userinfo.name, - { - "data": { - "oidc_token": data.token, - } - } - ); - return Promise.resolve( - { - "status_code": 200, - "data": lib_plankton.string.coin( - "", - { - "url": lib_plankton.string.coin( - data.redirect_uri_template, - { - "session_key": session_key, - } - ), - } - ), - } - ); - } + ), + } + ); }, } ); diff --git a/source/auth.ts b/source/auth.ts index cfd094b..82b797b 100644 --- a/source/auth.ts +++ b/source/auth.ts @@ -188,11 +188,7 @@ namespace _zeitbild.auth ) : Promise< { token : string; - userinfo : { - name : (null | string); - email : (null | string); - groups : (null | Array); - }; + userinfo : lib_plankton.auth.oidc.type_userinfo; redirect_uri_template : string; } > @@ -206,11 +202,7 @@ namespace _zeitbild.auth const state : string = data["state"]; const result : { token : string; - userinfo : { - name : (null | string); - email : (null | string); - groups : (null | Array); - }; + userinfo : lib_plankton.auth.oidc.type_userinfo; } = await lib_plankton.auth.oidc.handle_authorization_callback( _subject_oidc, cookie, @@ -219,11 +211,7 @@ namespace _zeitbild.auth return Promise.resolve< { token : string; - userinfo : { - name : (null | string); - email : (null | string); - groups : (null | Array); - }; + userinfo : lib_plankton.auth.oidc.type_userinfo; redirect_uri_template : string; } >( @@ -236,4 +224,144 @@ namespace _zeitbild.auth } } + + /** + * @todo switch for enabling/disabling auto provisioning + */ + export async function oidc_adapt_user( + userinfo : lib_plankton.auth.oidc.type_userinfo + ) + : Promise< + { + id : _zeitbild.type_user_id; + object : _zeitbild.type_user_object; + } + > + { + if (userinfo.name === null) + { + return Promise.reject(new Error("IDP did not return user name")); + } + else + { + // groups + const group_ids : Array<_zeitbild.type_group_id> = await (async () => { + const derive_name : ((group_name_raw : string) => string) = ( + (group_name_raw) => lib_plankton.string.coin( + "auto-{{name_raw}}", + { + "name_raw": group_name_raw, + } + ) + ); + const derive_label : ((group_name_raw : string) => string) = ( + (group_name_raw) => lib_plankton.string.coin( + "{{name_raw}}", + { + "name_raw": group_name_raw, + } + ) + ); + return Promise.all<_zeitbild.type_group_id>( + (userinfo.groups ?? []) + .map( + async (group_name_raw) => { + const group_name : string = derive_name(group_name_raw); + const group_id_raw : (null | _zeitbild.type_group_id) = await ( + _zeitbild.repository.group.identify(group_name) + .catch(() => Promise.resolve(null)) + ); + if (group_id_raw === null) + { + // create + const group_id : _zeitbild.type_group_id = await _zeitbild.service.group.add( + { + "name": group_name, + "label": derive_label(group_name_raw), + } + ); + lib_plankton.log.info( + "zeitbild.oidc_adapt_user.auto_provisioned_group", + { + "id": group_id, + "name": group_name, + } + ); + return group_id; + } + else + { + // update + const group_id : _zeitbild.type_group_id = group_id_raw; + await _zeitbild.service.group.change( + group_id, + { + "name": group_name, + "label": derive_label(group_name_raw), + } + ); + return group_id; + } + } + ) + ); + }) (); + + // user + const user : { + id : _zeitbild.type_user_id; + object : _zeitbild.type_user_object; + } = await (async () => { + const user_id_raw : (null | _zeitbild.type_user_id) = await ( + _zeitbild.service.user.identify(userinfo.name as string) + .catch(() => Promise.resolve(null)) + ); + if (user_id_raw === null) + { + // provision + const user_object : _zeitbild.type_user_object = { + "name": (userinfo.name as string), + "groups": group_ids, + "email_address": userinfo.email, + "dav_token": null, + }; + const user_id : _zeitbild.type_user_id = await _zeitbild.service.user.add( + user_object + ); + lib_plankton.log.info( + "user_provisioned", + { + "id": user_id, + "name": user_object.name, + } + ); + return {"id": user_id, "object": user_object}; + } + else + { + // update + const user_id : _zeitbild.type_user_id = user_id_raw; + const user_object : _zeitbild.type_user_object = await _zeitbild.service.user.get(user_id); + user_object.name = (userinfo.name as string); + user_object.groups = group_ids; + user_object.email_address = userinfo.email; + await _zeitbild.service.user.change( + user_id, + user_object + ); + lib_plankton.log.info( + "user_updated", + { + "id": user_id, + "name": user_object.name, + } + ); + return {"id": user_id, "object": user_object}; + } + }) (); + + return user; + } + } + } diff --git a/source/repositories/calendar.ts b/source/repositories/calendar.ts index 03e54e3..43266df 100644 --- a/source/repositories/calendar.ts +++ b/source/repositories/calendar.ts @@ -328,7 +328,6 @@ namespace _zeitbild.repository.calendar } - /** */ export async function read( @@ -601,7 +600,6 @@ namespace _zeitbild.repository.calendar /** - * @todo caching */ export async function overview( user_id : (null | _zeitbild.type_user_id) @@ -612,122 +610,152 @@ namespace _zeitbild.repository.calendar > > { + type type_data = { + hits_core : Array< + { + key : int; + preview : Record; + } + >; + hits_access_attributed_group : Array< + { + key : int; + preview : Record; + } + >; + hits_access_attributed_user : Array< + { + key : int; + preview : Record; + } + >; + }; return lib_plankton.cache.get_complex>( _zeitbild.cache_regular, "calendar_overview", { "user_id": user_id, }, - null, - () => ( - lib_plankton.file.read("sql/calendar_overview.sql") - .then( - (template) => _zeitbild.database.get_implementation().query_free_get( + 60, + async () => lib_plankton.call.convey( + { + "hits_core": await get_core_store().search( { - "template": template, - "arguments": { - "user_id": user_id, - } + "expression": "TRUE", + "arguments": {} } - ) - ) - .then( - (rows) => Promise.resolve( - lib_plankton.call.convey( - rows, - [ - (x : Array>) => x.map( - (row : Record) => ({ - "id": row["id"], - "name": lib_plankton.call.convey( - row["name"], - [ - // JSON.parse, - (x : Array) => x[0], - ] - ), - "hue": lib_plankton.call.convey( - row["hue"], - [ - // JSON.parse, - (x : Array) => x[0], - (x : int) => (x / hue_scaling), - ] - ), - /** - * @todo use _zeitbild.access_level_determine - */ - "access_level": _zeitbild.access_level_determine_raw( - lib_plankton.call.convey( - row["access_public"], + ), + "hits_access_attributed_group": await get_access_attributed_group_chest().search( + (user_id === null) + ? + { + "expression": "TRUE", + "arguments": {} + } + : + { + "expression": "(group_id IN (SELECT group_id FROM user_groups WHERE (user_id = $user_id)))", + "arguments": {"user_id": user_id} + } + ), + "hits_access_attributed_user": await get_access_attributed_user_chest().search( + (user_id === null) + ? + { + "expression": "TRUE", + "arguments": {} + } + : + { + "expression": "(user_id = $user_id)", + "arguments": {"user_id": user_id} + } + ), + }, + [ + // transform + (data : type_data) => data.hits_core.map( + (hit_core) => { + const calendar_id : _zeitbild.type_calendar_id = hit_core.key; + return { + "id": calendar_id, + "name": hit_core.preview["name"], + "hue": (hit_core.preview["hue"] / hue_scaling), + "access_level": _zeitbild.access_level_determine_raw( + hit_core.preview["access_public"], + ( + (user_id === null) + ? + null + : + { + "default": decode_access_level(hit_core.preview["access_level_default"]), + "group": lib_plankton.call.convey( + data.hits_access_attributed_group, [ - // JSON.parse, - (x : Array) => x[0], + (x : Array<{key : int; preview : Record}>) => x.filter( + hit_access_attributed_group => ( + (hit_access_attributed_group.preview.calendar_id === calendar_id) + ) + ), + (x : Array<{key : int; preview : Record}>) => x.map( + hit_access_attributed_group => hit_access_attributed_group.preview.level + ), + (x : Array) => x.map( + decode_access_level + ), ] ), - ( - (user_id === null) - ? - null - : - { - "default": lib_plankton.call.convey( - row["access_level_default"], - [ - // JSON.parse, - (x : Array) => x[0], - decode_access_level, - ] + "user": lib_plankton.call.convey( + data.hits_access_attributed_user, + [ + (x : Array<{key : int; preview : Record}>) => x.filter( + hits_access_attributed_user => ( + (hits_access_attributed_user.preview.calendar_id === calendar_id) + ) ), - "group": lib_plankton.call.convey( - row["access_level_attributed_group"], - [ - // JSON.parse, - (x : Array<(null | int)>) => x.filter(y => (y !== null)), - (x : Array) => x.map(decode_access_level), - ] + (x : Array<{key : int; preview : Record}>) => x.map( + hits_access_attributed_user => hits_access_attributed_user.preview.level ), - "user": lib_plankton.call.convey( - row["access_level_attributed_user"], - [ - // JSON.parse, - (x : Array<(null | int)>) => x.filter(y => (y !== null)), - (x : Array) => x.map(decode_access_level), - (x : Array<_zeitbild.enum_access_level>) => ((x.length > 0) ? x[0] : null), - ] + (x : Array) => x.map( + decode_access_level ), - } - ) - ), - }) - ), - (x : Array) => x.filter( - (row) => ( - ! _zeitbild.access_level_order( - row.access_level, - _zeitbild.enum_access_level.none - ) + (x : Array<_zeitbild.enum_access_level>) => (x[0] ?? null), + ] + ), + } ) ), - (x : Array) => lib_plankton.list.sorted( - x, - { - "compare_element": lib_plankton.order.order_lexicographic_pair_wrapped( - row => row.access_level, - row => row.id, - { - "order_first": (a, b) => _zeitbild.access_level_order(b, a), - "order_second": (a, b) => (a <= b) - } - ), - } - ), - ] + }; + } + ), + // only keep visible calendars + (x : Array) => x.filter( + (row) => ( + ! _zeitbild.access_level_order( + row.access_level, + _zeitbild.enum_access_level.none + ) ) - ) - ) + ), + // sort by access level and name + (x : Array) => lib_plankton.list.sorted( + x, + { + "compare_element": lib_plankton.order.order_lexicographic_pair_wrapped( + row => row.access_level, + row => row.id, + { + "order_first": (a, b) => _zeitbild.access_level_order(b, a), + "order_second": (a, b) => (a <= b) + } + ), + } + ), + ] ) ); } + } diff --git a/source/repositories/sql/calendar_overview.sql b/source/repositories/sql/calendar_overview.sql deleted file mode 100644 index 70a55b6..0000000 --- a/source/repositories/sql/calendar_overview.sql +++ /dev/null @@ -1,15 +0,0 @@ -SELECT - x.id AS id, - JSON_AGG(x.name) AS name, - JSON_AGG(x.hue) AS hue, - JSON_AGG(x.access_public) AS access_public, - JSON_AGG(x.access_level_default) AS access_level_default, - JSON_AGG(y1.level) AS access_level_attributed_group, - JSON_AGG(y2.level) AS access_level_attributed_user -FROM - calendars AS x - LEFT OUTER JOIN calendar_access_attributed_group AS y1 ON ((x.id = y1.calendar_id) AND (y1.group_id IN (SELECT group_id FROM user_groups WHERE (user_id = $user_id)))) - LEFT OUTER JOIN calendar_access_attributed_user AS y2 ON ((x.id = y2.calendar_id) AND (y2.user_id = $user_id)) -GROUP BY - x.id -; diff --git a/tools/makefile b/tools/makefile index d894ee2..bdbbd70 100644 --- a/tools/makefile +++ b/tools/makefile @@ -18,7 +18,7 @@ cmd_tsc := ${dir_tools}/typescript/node_modules/.bin/tsc ## rules .PHONY: default -default: node_modules sql ${dir_build}/zeitbild node_modules +default: node_modules ${dir_build}/zeitbild node_modules .PHONY: node_modules node_modules: @@ -26,13 +26,6 @@ node_modules: @ ${cmd_log} "node modules …" @ ${cmd_cp} -r -u ${dir_lib}/node/node_modules/* ${dir_build}/node_modules/ -.PHONY: sql -sql: \ - $(wildcard ${dir_source}/repositories/sql/*) - @ ${cmd_log} "sql …" - @ ${cmd_mkdir} ${dir_build}/sql - @ ${cmd_cp} -r -u $^ ${dir_build}/sql/ - ${dir_temp}/conf.ts: \ ${dir_source}/conf.ts.tpl \ ${dir_source}/conf.schema.json