Implemented basic SQL authentication module.
authorTomasz Sterna <tomek@xiaoka.com>
Tue, 12 Apr 2011 00:30:53 +0200
changeset 354 f24998ec7f8d
parent 353 8ef36af30181
child 355 a5da789b2e7d
Implemented basic SQL authentication module. This module implements authentication against plaintext password stored in SQL database. You wil definitely need to edit the Lua code and put a query working with your database. The example query works against jabberd2 database schema. P.S. This module is just some code glued together from other modules. ;-)
mod_auth_sql/mod_auth_sql.lua
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_auth_sql/mod_auth_sql.lua	Tue Apr 12 00:30:53 2011 +0200
@@ -0,0 +1,138 @@
+-- Simple SQL Authentication module for Prosody IM
+-- Copyright (C) 2011 Tomasz Sterna <tomek@xiaoka.com>
+--
+
+local log = require "util.logger".init("auth_sql");
+local new_sasl = require "util.sasl".new;
+local nodeprep = require "util.encodings".stringprep.nodeprep;
+
+local DBI;
+local connection;
+local host,user,store = module.host;
+local params = module:get_option("sql");
+
+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(false); -- don't commit automatically
+		connection = dbh;
+		return connection;
+	end
+end
+
+do -- process options to get a db connection
+	DBI = require "DBI";
+
+	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
+	-- 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
+
+function new_default_provider(host)
+	local provider = { name = "sql" };
+	log("debug", "initializing default authentication provider for host '%s'", host);
+
+	function provider.test_password(username, password)
+		log("debug", "test password '%s' for user %s at host %s", password, username, module.host);
+		return nil, "Password based auth not supported.";
+	end
+
+	function provider.get_password(username)
+		log("debug", "get_password for username '%s' at host '%s'", username, module.host);
+
+		local stmt, err = getsql("SELECT `password` FROM `authreg` WHERE `username`=? AND `realm`=?",
+			username, module.host);
+
+		local password = nil;
+		if stmt ~= nil then
+			for row in stmt:rows(true) do
+				password = row.password;
+			end
+		else
+			log("error", "QUERY ERROR: %s %s", err, debug.traceback());
+			return nil;
+		end
+
+		return password;
+	end
+
+	function provider.set_password(username, password)
+		return nil, "Password based auth not supported.";
+	end
+
+	function provider.user_exists(username)
+		return nil, "User exist check not supported.";
+	end
+
+	function provider.create_user(username, password)
+		return nil, "Account creation/modification not supported.";
+	end
+
+	function provider.get_sasl_handler()
+		local realm = module:get_option("sasl_realm") or module.host;
+		local getpass_authentication_profile = {
+			plain = function(sasl, username, realm)
+				local prepped_username = nodeprep(username);
+				if not prepped_username then
+					log("debug", "NODEprep failed on username: %s", username);
+					return "", nil;
+				end
+				local password = usermanager.get_password(prepped_username, realm);
+				if not password then
+					return "", nil;
+				end
+				return password, true;
+			end
+		};
+		return new_sasl(realm, getpass_authentication_profile);
+	end
+
+	return provider;
+end
+
+module:add_item("auth-provider", new_default_provider(module.host));
+