-- 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