Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
-- Based on Simple SQL Authentication module for Prosody IM
-- Copyright (C) 2011 Tomasz Sterna <tomek@xiaoka.com>
-- Copyright (C) 2011 Waqas Hussain <waqas20@gmail.com>
--
-- 25/05/2014: Modified for Diaspora by Anahuac de Paula Gil - anahuac@anahuac.eu
-- 06/08/2014: Cleaned up and fixed SASL auth by Jonne Haß <me@jhass.eu>
local log = require "util.logger".init("auth_diaspora");
local new_sasl = require "util.sasl".new;
local DBI = require "DBI"
local bcrypt = require "bcrypt"
local connection;
local params = module:get_option("auth_diaspora", module:get_option("auth_sql", 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(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("`", "\"");
elseif self.params.driver == "MySQL" then
sql = sql:gsub(";$", " CHARACTER SET 'utf8' COLLATE 'utf8_bin';");
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 get_password(username)
local stmt, err = getsql("SELECT encrypted_password FROM users WHERE username = ?", username);
if stmt then
for row in stmt:rows(true) do
return row.encrypted_password;
end
end
end
provider = {};
function provider.test_password(username, password)
-- pepper imported from diaspora/config/initializers/devise.rb
local pepper = "065eb8798b181ff0ea2c5c16aee0ff8b70e04e2ee6bd6e08b49da46924223e39127d5335e466207d42bf2a045c12be5f90e92012a4f05f7fc6d9f3c875f4c95b"
-- adding pepper to the regular password
local pw_plus_pepper = password .. pepper
-- Getting password from Diaspora database
local pw_stored = get_password(username)
-- Comparing password. If fail aborts
return password and pw_stored and bcrypt.verify(pw_plus_pepper, pw_stored)
end
function provider.get_password(username)
return get_password(username);
end
function provider.set_password(username, password)
return nil, "Setting password is not supported.";
end
function provider.user_exists(username)
return get_password(username) and true;
end
function provider.create_user(username, password)
return nil, "Account creation/modification not supported.";
end
function provider.get_sasl_handler()
local profile = {
plain_test = function(sasl, username, password, realm)
return provider.test_password(username, password), true;
end
};
return new_sasl(module.host, profile);
end
function provider.users()
local stmt, err = getsql("SELECT username FROM users WHERE username != ''");
if stmt then
local next, state = stmt:rows(true)
return function()
for row in next, state do
return row.username;
end
end
end
return stmt, err;
end
module:provides("auth", provider);
-- Simple SQL Authentication module for Prosody IM
-- Copyright (C) 2011 Tomasz Sterna <tomek@xiaoka.com>
-- Copyright (C) 2011 Waqas Hussain <waqas20@gmail.com>
--
local log = require "util.logger".init("auth_diaspora_chat");
local new_sasl = require "util.sasl".new;
local DBI = require "DBI"
local connection;
local params = module:get_option("auth_diaspora_chat", 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(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 get_password(username)
local stmt, err = getsql("SELECT `authentication_token` FROM `users` WHERE `username`=?", username);
if stmt then
for row in stmt:rows(true) do
module:log("debug", "username: %s, username in db %s, password in db: %s", tostring(username), tostring(row.username), tostring(row.authentication_token));
return row.authentication_token;
end
end
end
provider = {};
function provider.test_password(username, password)
module:log("debug", "username: %s, password: %s", username, password);
return password and get_password(username) == password;
end
function provider.get_password(username)
return get_password(username);
end
function provider.set_password(username, password)
return nil, "Setting password is not supported.";
end
function provider.user_exists(username)
return get_password(username) and true;
end
function provider.create_user(username, password)
return nil, "Account creation/modification not supported.";
end
function provider.get_sasl_handler()
local profile = {
plain = function(sasl, username, realm)
local password = get_password(username);
if not password then return "", nil; end
return password, true;
end
};
return new_sasl(module.host, profile);
end
function provider.users()
local stmt, err = getsql("SELECT `username` FROM `users`");
if stmt then
local next, state = stmt:rows(true)
return function()
for row in next, state do
return row.username;
end
end
end
return stmt, err;
end
module:provides("auth", provider);
-- Prosody module to import diaspora contacts into a users roster.
-- Inspired by mod_auth_sql and mod_groups of the Prosody software.
--
-- As with mod_groups the change is not permanent and thus any changes
-- to the imported contacts will be lost.
--
-- The MIT License (MIT)
--
-- Copyright (c) <2014> <Jonne Haß <me@jhass.eu>>
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.
local log = require "util.logger".init("diaspora_contacts");
local DBI = require "DBI"
local jid, datamanager = require "util.jid", require "util.datamanager";
local jid_prep = jid.prep;
local rostermanager = require "core.rostermanager";
local module_host = module:get_host();
local host = prosody.hosts[module_host];
local connection;
local params = module:get_option("diaspora_contacts", module:get_option("auth_diaspora", module:get_option("auth_sql", module:get_option("sql"))));
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
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("`", "\"");
elseif params.driver == "MySQL" then
sql = sql:gsub(";$", " CHARACTER SET 'utf8' COLLATE 'utf8_bin';");
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 get_contacts(username)
module:log("debug", "loading contacts for %s", username);
local contacts = {};
local stmt, err = getsql([[
SELECT people.diaspora_handle AS jid,
COALESCE(NULLIF(CONCAT(first_name, ' ', last_name), ' '), people.diaspora_handle) AS name,
CONCAT(aspects.name, ' (Diaspora)') AS group_name,
CASE
WHEN sharing = true AND receiving = true THEN 'both'
WHEN sharing = true AND receiving = false THEN 'to'
WHEN sharing = false AND receiving = true THEN 'from'
ELSE 'none'
END AS subscription
FROM contacts
JOIN people ON people.id = contacts.person_id
JOIN profiles ON profiles.person_id = people.id
JOIN users ON users.id = contacts.user_id
JOIN aspect_memberships ON aspect_memberships.contact_id = contacts.id
JOIN aspects ON aspects.id = aspect_memberships.aspect_id
WHERE (receiving = true OR sharing = true)
AND username = ?
]], username);
if stmt then
for row in stmt:rows(true) do
if not contacts[row.jid] then
contacts[row.jid] = {};
contacts[row.jid].subscription = row.subscription;
contacts[row.jid].name = row.name;
contacts[row.jid].groups = {};
end
contacts[row.jid].groups[row.group_name] = true
end
return contacts;
end
end
local function update_roster(roster, contacts, update_action)
if not contacts then return; end
for user_jid, contact in pairs(contacts) do
local updated = false;
if not roster[user_jid] then
roster[user_jid] = {};
roster[user_jid].subscription = contact.subscription;
roster[user_jid].name = contact.name;
roster[user_jid].persist = false;
updated = true;
end
if not roster[user_jid].groups then
roster[user_jid].groups = {};
end
for group in pairs(contact.groups) do
if not roster[user_jid].groups[group] then
roster[user_jid].groups[group] = true;
updated = true;
end
end
for group in pairs(roster[user_jid].groups) do
if not contact.groups[group] then
roster[user_jid].groups[group] = nil;
updated = true;
end
end
if updated and update_action then
update_action(user_jid);
end
end
end
local function update_roster_contacts(username, host, roster)
update_roster(roster, get_contacts(username), function (user_jid)
module:log("debug", "pushing roster update to %s for %s", jid.join(username, host), user_jid);
roster[false].version = (roster[false].version or 0) + 1;
rostermanager.roster_push(username, host, user_jid);
end)
end
function inject_roster_contacts(username, host, roster)
module:log("debug", "injecting contacts for %s", jid.join(username, host));
update_roster(roster, get_contacts(username));
if roster[false] then
roster[false].version = true;
end
end
function update_all_rosters()
module:log("debug", "updating all rosters");
for username, user in pairs(host.sessions) do
module:log("debug", "Updating roster for %s", jid.join(username, module_host));
update_roster_contacts(username, module_host, rostermanager.load_roster(username, module_host));
end
return 300;
end
function remove_virtual_contacts(username, host, datastore, data)
if host == module_host and datastore == "roster" then
module:log("debug", "removing injected contacts before storing roster of %s", jid.join(username, host));
local new_roster = {};
for jid, contact in pairs(data) do
if contact.persist ~= false then
new_roster[jid] = contact;
end
end
if new_roster[false] then
new_roster[false].version = nil; -- Version is void
end
return username, host, datastore, new_roster;
end
return username, host, datastore, data;
end
function module.load()
module:hook("roster-load", inject_roster_contacts);
module:add_timer(300, update_all_rosters);
datamanager.add_callback(remove_virtual_contacts);
end
function module.unload()
datamanager.remove_callback(remove_virtual_contacts);
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment