Mercurial > prosody-modules
view mod_auth_wordpress/mod_auth_wordpress.lua @ 5448:9d542e86e19a
mod_http_oauth2: Allow requesting a subset of scopes on token refresh
This enables clients to request access tokens with fewer permissions
than the grant they were given, reducing impact of token leak. Clients
could e.g. request access tokens with some privileges and immediately
revoke them after use, or other strategies.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Thu, 11 May 2023 21:40:09 +0200 |
parents | 7dbde05b48a9 |
children |
line wrap: on
line source
-- Wordpress authentication backend for Prosody -- -- Copyright (C) 2011 Waqas Hussain -- Copyright (C) 2011 Kim Alvefur -- local log = require "util.logger".init("auth_sql"); local new_sasl = require "util.sasl".new; local nodeprep = require "util.encodings".stringprep.nodeprep; local saslprep = require "util.encodings".stringprep.saslprep; local DBI = require "DBI" local md5 = require "util.hashes".md5; local uuid_gen = require "util.uuid".generate; local connection; local params = module:get_option("sql"); local table_prefix = module:get_option_string("wordpress_table_prefix", "wp_"); local resolve_relative_path = require "core.configmanager".resolve_relative_path; local function test_connection() if not connection then return nil; end if connection:ping() then return true; else module:log("debug", "Database connection closed"); connection = nil; end end local function connect() if not test_connection() then prosody.unlock_globals(); local dbh, err = DBI.Connect( params.driver, params.database, params.username, params.password, params.host, params.port ); prosody.lock_globals(); if not dbh then module:log("debug", "Database connection failed: %s", tostring(err)); return nil, err; end module:log("debug", "Successfully connected to database"); dbh:autocommit(true); -- don't run in transaction connection = dbh; return connection; end end do -- process options to get a db connection params = params or { driver = "SQLite3" }; if params.driver == "SQLite3" then params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite"); end assert(params.driver and params.database, "Both the SQL driver and the database need to be specified"); assert(connect()); end local function getsql(sql, ...) if params.driver == "PostgreSQL" then sql = sql:gsub("`", "\""); end if not test_connection() then connect(); end -- do prepared statement stuff local stmt, err = connection:prepare(sql); if not stmt and not test_connection() then error("connection failed"); end if not stmt then module:log("error", "QUERY FAILED: %s %s", err, debug.traceback()); return nil, err; end -- run query local ok, err = stmt:execute(...); if not ok and not test_connection() then error("connection failed"); end if not ok then return nil, err; end return stmt; end local function setsql(sql, ...) local stmt, err = getsql(sql, ...); if not stmt then return stmt, err; end return stmt:affected(); end local function get_password(username) local stmt, err = getsql("SELECT `user_pass` FROM `"..table_prefix.."users` WHERE `user_login`=?", username); if stmt then for row in stmt:rows(true) do return row.user_pass; end end end local itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; local function hashEncode64(input, count) local output = ""; local i, value = 0, 0; while true do value = input:byte(i+1) i = i+1; local idx = value % 0x40 + 1; output = output .. itoa64:sub(idx, idx); if i < count then value = value + input:byte(i+1) * 256; end local _ = value % (2^6); local idx = ((value - _) / (2^6)) % 0x40 + 1 output = output .. itoa64:sub(idx, idx); if i >= count then break; end i = i+1; if i < count then value = value + input:byte(i+1) * 256 * 256; end local _ = value % (2^12); local idx = ((value - _) / (2^12)) % 0x40 + 1 output = output .. itoa64:sub(idx, idx); if i >= count then break; end i = i+1; local _ = value % (2^18); local idx = ((value - _) / (2^18)) % 0x40 + 1 output = output .. itoa64:sub(idx, idx); if not(i < count) then break; end end return output; end local function hashCryptPrivate(password, genSalt) local output = "*"; if not genSalt:match("^%$P%$") then return output; end local count_log2 = itoa64:find(genSalt:sub(4,4)) - 1; if count_log2 < 7 or count_log2 > 30 then return output; end local count = 2 ^ count_log2; local salt = genSalt:sub(5, 12); if #salt ~= 8 then return output; end local hash = md5(salt..password); while true do hash = md5(hash..password); if not(count > 1) then break; end count = count-1; end output = genSalt:sub(1, 12); output = output .. hashEncode64(hash, 16); return output; end local function hashGensaltPrivate(input) local iteration_count_log2 = 6; local output = "$P$"; local idx = math.min(iteration_count_log2 + 5, 30) + 1; output = output .. itoa64:sub(idx, idx); output = output .. hashEncode64(input, 6); return output; end local function wordpressCheckHash(password, hash) if #hash == 32 then return hash == md5(password, true); end return #hash == 34 and hashCryptPrivate(password, hash) == hash; end local function wordpressCreateHash(password) local random = uuid_gen():sub(-6); local salt = hashGensaltPrivate(random); local hash = hashCryptPrivate(password, salt); if #hash == 34 then return hash; end return md5(password, true); end provider = {}; function provider.test_password(username, password) local hash = get_password(username); return hash and wordpressCheckHash(password, hash); end function provider.user_exists(username) module:log("debug", "test user %s existence", username); return get_password(username) and true; end function provider.get_password(username) return nil, "Getting password is not supported."; end function provider.set_password(username, password) local hash = wordpressCreateHash(password); local stmt, err = setsql("UPDATE `"..table_prefix.."users` SET `user_pass`=? WHERE `user_login`=?", hash, username); return stmt and true, err; end function provider.create_user(username, password) return nil, "Account creation/modification not supported."; end local escapes = { [" "] = "\\20"; ['"'] = "\\22"; ["&"] = "\\26"; ["'"] = "\\27"; ["/"] = "\\2f"; [":"] = "\\3a"; ["<"] = "\\3c"; [">"] = "\\3e"; ["@"] = "\\40"; ["\\"] = "\\5c"; }; local unescapes = {}; for k,v in pairs(escapes) do unescapes[v] = k; end local function jid_escape(s) return s and (s:gsub(".", escapes)); end local function jid_unescape(s) return s and (s:gsub("\\%x%x", unescapes)); end function provider.get_sasl_handler() local sasl = {}; function sasl:clean_clone() return provider.get_sasl_handler(); end function sasl:mechanisms() return { PLAIN = true; }; end function sasl:select(mechanism) if not self.selected and mechanism == "PLAIN" then self.selected = mechanism; return true; end end function sasl:process(message) if not message then return "failure", "malformed-request"; end local authorization, authentication, password = message:match("^([^%z]*)%z([^%z]+)%z([^%z]+)"); if not authorization then return "failure", "malformed-request"; end authentication = saslprep(authentication); password = saslprep(password); if (not password) or (password == "") or (not authentication) or (authentication == "") then return "failure", "malformed-request", "Invalid username or password."; end local function test(authentication) local prepped = nodeprep(authentication); local normalized = jid_unescape(prepped); return normalized and provider.test_password(normalized, password) and prepped; end local username = test(authentication) or test(jid_escape(authentication)); if username then self.username = username; return "success"; end return "failure", "not-authorized", "Unable to authorize you with the authentication credentials you've sent."; end return sasl; end module:provides("auth", provider);