Mercurial > prosody-modules
view mod_net_dovecotauth/mod_net_dovecotauth.lua @ 5418:f2c7bb3af600
mod_http_oauth2: Add role selector to consent page
List includes all roles available to the user, if more than one.
Defaults to either the first role in the scope string or the users
primary role.
Earlier draft listed all roles, but having options that can't be
selected is bad UX and the entire list of all roles on the server could
be long, and perhaps even sensitive.
Allows e.g. picking a role with fewer permissions than what might
otherwise have been selected.
UX wise, doing this with more checkboxes or possibly radio buttons would
have been confusion and/or looked messier.
Fixes the previous situation where unselecting a role would default to
the primary role, which could be more permissions than requested.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Fri, 05 May 2023 01:23:13 +0200 |
parents | 8e686bf63441 |
children |
line wrap: on
line source
-- mod_net_dovecotauth.lua -- -- Protocol spec: -- http://dovecot.org/doc/auth-protocol.txt -- -- Example postfix config: -- sudo postconf smtpd_sasl_path=inet:127.0.0.1:28484 -- sudo postconf smtpd_sasl_type=dovecot -- sudo postconf smtpd_sasl_auth_enable=yes module:set_global(); -- Imports local new_sasl = require "core.usermanager".get_sasl_handler; local user_exists = require "core.usermanager".user_exists; local base64 = require"util.encodings".base64; local dump = require"util.serialization".serialize; local pposix = require "util.pposix"; -- Config local default_vhost = module:get_option_string("dovecotauth_host", (next(hosts))); -- TODO Is there a better solution? local allow_master = module:get_option_boolean("dovecotauth_allow_master", false); -- Active sessions local sessions = {}; -- Session methods local new_session; do local sess = { }; local sess_mt = { __index = sess }; function new_session(conn) local s = { type = "?", conn = conn, buf = "", sasl = {} } function s:log(l, m, ...) return module:log(l, self.type..tonumber(tostring(self):match("%x+$"), 16)..": "..m, ...); end return setmetatable(s, sess_mt); end function sess:send(...) local data = table.concat({...}, "\t") .. "\n" -- self:log("debug", "SEND: %s", dump(ret)); return self.conn:write(data); end local mech_params = { ANONYMOUS = "anonymous"; PLAIN = "plaintext"; ["DIGEST-MD5"] = "mutual-auth"; ["SCRAM-SHA-1"] = "mutual-auth"; ["SCRAM-SHA-1-PLUS"] = "mutual-auth"; } function sess:handshake() self:send("VERSION", 1, 1); self:send("SPID", pposix.getpid()); self:send("CUID", tonumber(tostring(self):match"%x+$", 16)); for mech in pairs(self.g_sasl:mechanisms()) do self:send("MECH", mech, mech_params[mech]); end self:send("DONE"); end function sess:feed(data) -- TODO break this up a bit -- module:log("debug", "sess = %s", dump(self)); local buf = self.buf; buf = buf .. data; local line, eol = buf:match("(.-)\r?\n()") while line and line ~= "" do buf = buf:sub(eol); self.buf = buf; local part = line:gmatch("[^\t]+"); local command = part(); if command == "VERSION" then local major = tonumber(part()); local minor = tonumber(part()); if major ~= 1 then self:log("warn", "Wrong version, expected 1.1, got %s.%s", tostring(major), tostring(minor)); self.conn:close(); break; end elseif command == "CPID" then self.type = "C"; self.pid = part(); elseif command == "SPID" and allow_master then self.type = "M"; self.pid = part(); elseif command == "AUTH" and self.type ~= "?" then -- C: "AUTH" TAB <id> TAB <mechanism> TAB service=<service> [TAB <parameters>] local id = part() -- <id> local sasl = self.sasl[id]; local mech = part(); if not sasl then -- TODO Should maybe initialize SASL handler after parsing the line? sasl = self.g_sasl:clean_clone(); self.sasl[id] = sasl; if not sasl:select(mech) then self:send("FAIL", id, "reason=invalid-mechanism"); self.sasl[id] = nil; sasl = false end end if sasl then local params = {}; -- Not used for anything yet for p in part do local k,v = p:match("^([^=]*)=(.*)$"); if k == "resp" then self:log("debug", "params = %s", dump(params)); v = base64.decode(v); local status, ret, err = sasl:process(v); self:log("debug", status); if status == "challenge" then self:send("CONT", id, base64.encode(ret)); elseif status == "failure" then self.sasl[id] = nil; self:send("FAIL", id, "reason="..tostring(err)); elseif status == "success" then self.sasl[id] = nil; self:send("OK", id, "user="..sasl.username, ret and "resp="..base64.encode(ret)); end break; -- resp MUST be the last param else params[k or p] = v or true; end end end elseif command == "USER" and self.type == "M" then -- FIXME Should this be on a separate listener? local id = part(); local user = part(); if user and user_exists(user, default_vhost) then self:send("USER", id); else self:send("NOTFOUND", id); end else self:log("warn", "Unhandled command %s", tostring(command)); self.conn:close(); break; end line, eol = buf:match("(.-)\r?\n()") end end end local listener = {} function listener.onconnect(conn) local s = new_session(conn); sessions[conn] = s; local g_sasl = new_sasl(default_vhost, s); s.g_sasl = g_sasl; s:handshake(); end function listener.onincoming(conn, data) local s = sessions[conn]; -- s:log("debug", "RECV %s", dump(data)); return s:feed(data); end function listener.ondisconnect(conn) sessions[conn] = nil; end function module.unload() for c in pairs(sessions) do c:close(); end end module:provides("net", { default_port = 28484; listener = listener; });