changeset 5350:f8ec43db580b

mod_oidc_userinfo_vcard4: Provide profile details in mod_http_oauth2
author Kim Alvefur <zash@zash.se>
date Mon, 17 Apr 2023 08:01:09 +0200 (20 months ago)
parents ac9710126e1a
children c35f3c1762b5
files mod_oidc_userinfo_vcard4/README.md mod_oidc_userinfo_vcard4/mod_oidc_userinfo_vcard4.lua
diffstat 2 files changed, 99 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_oidc_userinfo_vcard4/README.md	Mon Apr 17 08:01:09 2023 +0200
@@ -0,0 +1,19 @@
+---
+summary: OIDC UserInfo profile details from vcard4
+labels:
+- Stage-Alpha
+rockspec:
+  dependencies:
+  - mod_http_oauth2
+---
+
+This module extracts profile details from the user's [vcard4][XEP-0292]
+and provides them in the [UserInfo] endpoint of [mod_http_oauth2] to
+clients the user grants authorization.
+
+Whether this is really needed is unclear at this point. When logging in
+with an XMPP client, it could fetch the actual vcard4 to retrieve these
+details, so the UserInfo details would probably primarily be useful to
+other OAuth 2 and OIDC clients.
+
+[UserInfo]: https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_oidc_userinfo_vcard4/mod_oidc_userinfo_vcard4.lua	Mon Apr 17 08:01:09 2023 +0200
@@ -0,0 +1,80 @@
+-- Provide OpenID UserInfo data to mod_http_oauth2
+-- Alternatively, separate module for the whole HTTP endpoint?
+--
+local nodeprep = require "util.encodings".stringprep.nodeprep;
+
+local mod_pep = module:depends "pep";
+
+local gender_map = { M = "male"; F = "female"; O = "other"; N = "nnot applicable"; U = "unknown" }
+
+module:hook("token/userinfo", function(event)
+	local pep_service = mod_pep.get_pep_service(event.username);
+
+	local vcard4 = select(3, pep_service:get_last_item("urn:xmpp:vcard4", true));
+
+	local userinfo = event.userinfo;
+	if vcard4 and event.claims:contains("profile") then
+		userinfo.name = vcard4:find("fn/text#");
+		userinfo.family_name = vcard4:find("n/surname#");
+		userinfo.given_name = vcard4:find("n/given#");
+		userinfo.middle_name = vcard4:find("n/additional#");
+
+		userinfo.nickname = vcard4:find("nickname/text#");
+		if not userinfo.nickname then
+			local ok, _, nick_item = pep_service:get_last_item("http://jabber.org/protocol/nick", true);
+			if ok and nick_item then
+				userinfo.nickname = nick_item:get_child_text("nick", "http://jabber.org/protocol/nick");
+			end
+		end
+
+		userinfo.preferred_username = event.username;
+
+		-- profile -- page? not their website
+		-- picture -- mod_http_pep_avatar?
+		userinfo.website = vcard4:find("url/uri#");
+		userinfo.birthdate = vcard4:find("bday/date#");
+		userinfo.zoneinfo = vcard4:find("tz/text#");
+		userinfo.locale = vcard4:find("lang/language-tag#");
+
+		userinfo.gender = gender_map[vcard4:find("gender/sex#")] or vcard4:find("gender/text#");
+
+		-- updated_at -- we don't keep a vcard change timestamp?
+	end
+
+	if not userinfo.nickname and event.claims:contains("profile") then
+		local ok, _, nick_item = pep_service:get_last_item("http://jabber.org/protocol/nick", true);
+		if ok and nick_item then
+			userinfo.nickname = nick_item:get_child_text("nick", "http://jabber.org/protocol/nick");
+		end
+	end
+
+	if vcard4 and event.claims:contains("email") then
+		userinfo.email = vcard4:find("email/text#")
+		if userinfo.email then
+			userinfo.email_verified = false;
+		end
+	end
+
+	if vcard4 and event.claims:contains("address") then
+		local adr = vcard4:get_child("adr");
+		if adr then
+			userinfo.address = {
+				formatted = nil;
+				street_address = adr:get_child_text("street");
+				locality = adr:get_child_text("locality");
+				region = adr:get_child_text("region");
+				postal_code = adr:get_child_text("code");
+				country = adr:get_child_text("country");
+			}
+		end
+	end
+
+	if vcard4 and event.claims:contains("phone") then
+		userinfo.phone = vcard4:find("email/text#")
+		if userinfo.phone then
+			userinfo.phone_number_verified = false;
+		end
+	end
+
+
+end, 10);