diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b937d0b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Force our line endings to be LF, even for Windows +* text eol=lf \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13bbe5b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ + diff --git a/Auth_SMF.php b/Auth_SMF.php deleted file mode 100644 index cd2cca5..0000000 --- a/Auth_SMF.php +++ /dev/null @@ -1,1157 +0,0 @@ - ../forum -$wgSMFPath = "../forum"; - -# Use SMF's login system to automatically log you in/out of the wiki -# This works best if you are using SMF database sessions (default). -# Make sure "Use database driven sessions" is checked in the -# SMF Admin -> Server Settings -> Feature Configuration section -# NOTE: Make sure to configure the wgCookeDomain below -#$wgSMFLogin = true; -#$wgCookieDomain = 'domain.com'; - -# Members in these SMF groups will not be allowed to sign into wiki. -# This is useful for denying access to wiki and a easy anti-spam -# method. The group ID, which can be found in the url (;group=XXX) -# when viewing the group from the administrator control panel. -#$wgSMFDenyGroupID = array(4); - -# Grant members of this SMF group(s) access to the wiki -# NOTE: The wgSMFDenyGroupID group supersedes this. -#wgSMFGroupID = array(2); - -# Grant members of this SMF group(s) wiki sysop privileges -# NOTE: These members must be able to login to the wiki -#$wgSMFAdminGroupID = array(1, 3); - -# SMF to wiki group translation. This allows us to assign wiki groups -# to those in certain SMF groups. -#$wgSMFSpecialGroups = array( -# // SMF Group ID => Wiki group name. -# 5 => 'autoconfirmed', -#); - -# THIS MUST BE ADDED. This prevents direct access to the Auth file. -define('SMF_IN_WIKI', true); - -# Load up the extension -require_once "$IP/extensions/Auth_SMF.php"; -$wgAuth = new Auth_SMF(); - - */ -if (!defined('SMF_IN_WIKI')) - exit('Hacking attempt on SMF...'); - -// Only usefull for debugging purposes. -$wgSMFDebug = false; - -// More information if we are debugging. -if ($wgSMFDebug) - error_reporting(E_ALL); - -if (file_exists("$wgSMFPath/Settings.php")) -{ - // If you are upgrading the wiki, loading SMF will break it because it defines $maintenace as well. - if (isset($maintenance)) - $mediawiki_maintenance = $maintenance; - - require_once("$wgSMFPath/Settings.php"); - - // Restore mediawiki $maintenance if needed. - $smf_maintenance = $maintenance; - if (isset($mediawiki_maintenance)) - $maintenance = $mediawiki_maintenance; -} -else - die('Check to make sure $wgSMFPath is correctly set in LocalSettings.php!'); - -$smf_settings['boardurl'] = $boardurl; -$smf_settings['cookiename'] = $cookiename; -$smf_settings['db_server'] = $db_server; -$smf_settings['db_name'] = $db_name; -$smf_settings['db_user'] = $db_user; -$smf_settings['db_passwd'] = $db_passwd; -$smf_settings['db_prefix'] = $db_prefix; -$smf_settings['debug_wiki'] = $wgSMFDebug; -$smf_settings['sourcedir'] = $sourcedir; -$smf_settings['db_type'] = $db_type; -$smf_settings['auth_secret'] = $auth_secret; -$smf_settings['cookie_no_auth_secret'] = isset($cookie_no_auth_secret) ? $cookie_no_auth_secret : false; - -/** - * Check the SMF cookie and automatically log the user into the wiki. - * - * @param User $user - * @return bool - * @public - */ -function AutoAuthenticateSMF ($initial_user_data, &$user) -{ - global $wgAuth, $smf_settings, $modSettings, $smf_member_id, $user_settings, $ID_MEMBER; - - // As to why we need to do this makes no sense really. - // Thanks to Norv of SimpleMachines.org for the fix. - $ID_MEMBER = 0; - $user = $initial_user_data; - - if (isset($_COOKIE[$smf_settings['cookiename']])) - { - $_COOKIE[$smf_settings['cookiename']] = stripslashes($_COOKIE[$smf_settings['cookiename']]); - - // MediaWiki doesn't support PHP4 since 1.6.12, so no check for a security issue is needed. - list ($ID_MEMBER, $password) = @unserialize($_COOKIE[$smf_settings['cookiename']]); - $ID_MEMBER = !empty($ID_MEMBER) && strlen($password) > 0 ? (int) $ID_MEMBER : 0; - } - - // Only load this stuff if the user isn't a guest. - if ($ID_MEMBER != 0) - { - if (!empty($smf_settings['debug_wiki']) || (empty($_SESSION['user_settings']) || empty($_SESSION['user_settings_time']) || time() > $_SESSION['user_settings_time'] + 900)) - { - $request = $wgAuth->db_query(" - SELECT id_member, member_name, email_address, real_name, - is_activated, passwd, password_salt, - id_group, id_post_group, additional_groups - FROM $smf_settings[db_prefix]members - WHERE id_member = '{$ID_MEMBER}' - AND is_activated = 1 - LIMIT 1"); - - $user_settings = $wgAuth->db_fetch_assoc($request); - - $_SESSION['user_settings'] = serialize($user_settings); - $wgAuth->db_free_result($request); - } - else - $user_settings = unserialize($_SESSION['user_settings']); - - // Did we find 'im? If not, junk it. - if (!empty($user_settings)) - { - // SHA-1 passwords should be 40 characters long. - if (strlen($password) == 40 && empty($smf_settings['cookie_no_auth_secret']) && !empty($smf_settings['auth_secret'])) - $check = hash_hmac('sha1', sha1($user_settings['passwd'] . $user_settings['password_salt']), $smf_settings['auth_secret']) == $password; - elseif (strlen($password) == 40) - $check = sha1($user_settings['passwd'] . $user_settings['password_salt']) == $password; - else - $check = false; - - // Wrong password or not activated - either way, you're going nowhere. - $ID_MEMBER = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? $user_settings['id_member'] : 0; - } - else - $ID_MEMBER = 0; - - // This just simplifies things further on. - $user_settings['smf_groups'] = array_merge(array($user_settings['id_group'], $user_settings['id_post_group']), explode(',', $user_settings['additional_groups'])); - } - - // Log out guests or members with invalid cookie passwords. - if ($ID_MEMBER == 0) - { - // A bug seems to exist in isLoggedIn when it calls getId. - // getId appears to try to load user data, which may not exist at this point. - // Why getId just doesn't return $this->mId, I have no idea. - $user->doLogout(); - return false; - } - - // Do we know the SMF member id yet? - if (empty($smf_member_id)) - $smf_member_id = $user->getOption('smf_member_id'); - - // If the username has an underscore or space accept the first registered user. - if (empty($smf_member_id) && (strpos($user_settings['member_name'], ' ') !== false || strpos($user_settings['member_name'], '_') !== false)) - { - $request = $wgAuth->db_query(" - SELECT id_member - FROM $smf_settings[db_prefix]members - WHERE member_name = '" . $user_settings['member_name'] . "' - ORDER BY date_registered ASC - LIMIT 1"); - - list($id) = $wgAuth->db_fetch_row($request); - $wgAuth->db_free_result($request); - - // Sorry your name was taken already! - if ($id != $ID_MEMBER) - { - if($user->isLoggedIn()) - $user->logout(); - return true; - } - } - - // Lastly check to see if they are not banned and allowed to login - if (!$wgAuth->isNotBanned($ID_MEMBER) || !$wgAuth->canLogin()) - { - if ($user->isLoggedIn()) - $user->logout(); - return true; - } - - // Mediawiki username rules: https://www.mediawiki.org/wiki/Thread:Project:Support_desk/Username_rules - // MediaWiki Username are always capatlized, considers spaces and _ the same - // MediaWiki disallows #<>[]|{}, non-printable characters 0 through 31, and 127 - // SMF disallows <>&"'=\ - // Because of SMF and MediaWiki naming differences, we replace and adjust the naming to be compatible with MediaWiki, while allowing a reverse engineering to so speak. - $username = ucfirst(str_replace('_', '\'', $user_settings['member_name'])); - $username = strtr($username, array('[' => '=', ']' => '"', '|' => '&', '#' => '\\')); - $username = strtr($username, array('{' => '==', '}' => '""')); - - // Only poll the database if no session or username mismatch. - if (!($user->isLoggedIn() && $user->getName() == $username)) - { - $user->setId($user->idFromName($username)); - - // No ID we need to add this member to the wiki database. - if ($user->getID() == 0) - { - // getID clears out the name set above. - $user->setName($username); - $user->setEmail($user_settings['email_address']); - $user->setRealName($user_settings['real_name']); - - // Let wiki know that their email has been verified. - $user->mEmailAuthenticated = wfTimestampNow(); - - // Finally create the user. - $user->addToDatabase(); - - // Don't worry about clearing the cache, the setEmail will do that. - $user->setOption('smf_member_id', $ID_MEMBER); - $user->setOption('smf_last_update', time()); - - // Some reason addToDatabase doesn't set options. So we do this manually. - $user->saveSettings(); - } - } - - // Do we know the SMF member id yet? - if (empty($smf_member_id)) - $smf_member_id = $user->getOption('smf_member_id'); - - // We have tried all we can, but the data just doesn't match up. - if (empty($smf_member_id) || $smf_member_id != $ID_MEMBER) - { - // TODO: Log errors if the ids don't match? - - if ($user->isLoggedIn()) - $user->logout(); - return true; - } - - // Keep their email and real name up to date with SMF - $last_update = (int) $user->getOption('smf_last_update', 0); - - if (empty($last_update) || time() > ($last_update + 900)) - { - $user->setEmail($user_settings['email_address']); - $user->setRealName($user_settings['real_name']); - - // We have some sort of group change. - $wgAuth->isGroupAllowed($user_settings['member_name'], $user); - $wgAuth->setAdminGroup($user, $smf_member_id); - - // Save! - $user->setOption('smf_last_update', time()); - $user->saveSettings(); - } - - // Go ahead and log 'em in, but only if the session isn't already going. - if (session_id() === '') - { - wfSetupSession(); - $user->setCookies(); - } - - return true; -} - -/** - * Redirect them to the SMF login page. - * - * @param User $user - * @public - */ -function UserLoginFormSMF (&$user) -{ - smf_sessionSetup(); - smf_redirectWrapper('old_url', 'login'); -} - -/** - * Redirect and utilize the SMF logout function. - * This also destroys the wiki session, preventing issues - * where wiki still believes a user is logged in. - * - * @param User $user - * @public - */ -function UserLogoutSMF (&$user) -{ - global $wgCookiePrefix, $wgSessionName; - - // Log them out of wiki first. - $user->doLogout(); - - // Destory their session. - $wgCookiePrefix = strtr($wgCookiePrefix, "=,; +.\"'\\[", "__________"); - $old_session = session_name(isset($wgSessionName) ? $wgSessionName : $wgCookiePrefix . '_session'); - session_destroy(); - - // Destroy the cookie! - $params = session_get_cookie_params(); - setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly']); - - // Back to whatever we had (we hope mediawiki). - session_name($old_session); - - // Now SMFs turn. - smf_sessionSetup(); - - // This means we have no SMF session data or unable to find it. - if (empty($_SESSION['session_var'])) - return true; - - smf_redirectWrapper('logout_url', 'logout;' . $_SESSION['session_var'] . '=' . $_SESSION['session_value']); -} - -/** - * Redirect and utilize the SMF register function. - * - * @public - */ -function UserRegisterSMF (&$template) -{ - smf_sessionSetup(); - smf_redirectWrapper('old_url', 'register'); -} - -/** - * Wrapper to configure the SMF session and perform the redirect. - * - * @public - */ -function smf_redirectWrapper($session, $action) { - global $wgScriptPath, $smf_settings; - - $page = !empty($_GET['returnto']) ? '?title=' . urlencode($_GET['returnto']) . '&' : '?'; - $_SESSION[$session] = 'http://' . $_SERVER['SERVER_NAME'] . $wgScriptPath . '/index.php' . $page . 'board=redirect'; - - // Do the actual redirect. - header ('Location: ' . $smf_settings['boardurl'] . '/index.php?action=' . $action); - exit(); -} - -/** - * If the user has visited the forum during the browser session - * then load up the exisiting session. Otherwise start a new - * session that SMF can use. - * - * @public - */ -function smf_sessionSetup() -{ - global $wgSessionsInMemcached, $wgCookieDomain, $smf_settings; - - // Clean out the existing session. This should have no affect - // since we are going to redirct the user to the SMF page. - @session_write_close(); - - // We can guess if wiki is using memcache sessions, so is SMF. - if ($wgSessionsInMemcached) - @ini_set('session.save_handler', 'memcache'); - - // Why MediaWiki doesn't store the original. - $old_session = session_name(); - session_name(ini_get('session.name')); - - // Start your engines. - session_start(); - - // Load up the SMF session and set the redirect URL. - if (isset($_COOKIE[$smf_settings['cookiename']])) - session_decode($_COOKIE[$smf_settings['cookiename']]); - // No exisiting session, create one - else - { - // Grab us a unique ID for SMF. - session_regenerate_id(); - - // Needed for SMF checks. - $_SESSION['rand_code'] = md5(session_id() . rand()); - $_SESSION['USER_AGENT'] = $_SERVER['HTTP_USER_AGENT']; - - // Set the cookie. - $data = serialize(array(0, '', 0)); - setcookie($smf_settings['cookiename'], $data, time() + 3600, '/', $wgCookieDomain, 0); - } - - // Restore the old session. - session_name($old_session); -} - -// First check if class has already been defined. -if (!class_exists('AuthPlugin')) - require_once "$IP/includes/AuthPlugin.php"; - -class Auth_SMF extends AuthPlugin -{ - var $conn = 0; - - /** - * Class constructor that will initialize the hooks and database connection. - */ - function __construct() - { - global $wgSMFLogin, $wgHooks, $wgDefaultUserOptions; - - // Integrate with SMF login / logout? - if (isset($wgSMFLogin) && $wgSMFLogin) - { - $wgHooks['AutoAuthenticate'][] = 'AutoAuthenticateSMF'; - $wgHooks['UserLoadFromSession'][] = 'AutoAuthenticateSMF'; - $wgHooks['UserLoginForm'][] = 'UserLoginFormSMF'; - $wgHooks['UserLogout'][] = 'UserLogoutSMF'; - } - - // Default some settings we us. - $wgDefaultUserOptions['smf_member_id'] = 0; - $wgDefaultUserOptions['smf_last_update'] = 0; - - // Always redirect registration to SMF. - $wgHooks['UserCreateForm'][] = 'UserRegisterSMF'; - - // Connect to the database. - $this->connect(); - } - - /** - * Check whether there exists a user account with the given name. - * The name will be normalized to MediaWiki's requirements, so - * you might need to munge it (for instance, for lowercase initial - * letters). - * - * @param $username String: username. - * @return bool - * @public - */ - public function userExists($username) - { - global $smf_settings, $smf_member_id; - - // Check if we did this already recently. - if (empty($smf_settings['debug_wiki']) && isset($_SESSION['smf_uE_t'], $_SESSION['smf_uE']) && time() < ($_SESSION['smf_uE'] + 300)) - return $_SESSION['smf_uE']; - $_SESSION['smf_uE'] = time(); - - $username = $this->fixUsername($username); - $request = $this->db_query(" - SELECT member_name - FROM $smf_settings[db_prefix]members - WHERE id_member = '{$smf_member_id}' - LIMIT 1"); - - list ($user) = $this->db_fetch_row($request); - $this->db_free_result($request); - - // Play it safe and double check the match. - $_SESSION['smf_uE'] = strtolower($user) == strtolower($username) ? true : false; - - return $_SESSION['smf_uE']; - } - - /** - * Check if a username+password pair is a valid login. - * The name will be normalized to MediaWiki's requirements, so - * you might need to munge it (for instance, for lowercase initial - * letters). - * - * @param $username String: username. - * @param $password String: user password. - * @return bool - * @public - */ - public function authenticate($username, $password) - { - global $smf_settings, $smf_member_id; - - // No id, you must be unauthorized. - if ($smf_member_id == 0) - return false; - - $username = $this->fixUsername($username); - $request = $this->db_query(" - SELECT member_name, passwd - FROM $smf_settings[db_prefix]members - WHERE id_member = '{$smf_member_id}' - AND is_activated = 1 - LIMIT 1"); - - list($member_name, $passwd) = $this->db_fetch_row($request); - $this->db_free_result($request); - - $pw = sha1(strtolower($username) . $password); - - // Check for password match, the user is not banned, and the user is allowed. - if($pw == $passwd && $this->isNotBanned($smf_member_id) && $this->isGroupAllowed($username)) - return true; - - return false; - } - - /** - * Modify options in the login template. - * - * @param $template UserLoginTemplate object. - * @public - */ - public function modifyUITemplate(&$template, &$type) - { - $template->set('usedomain', false); // We do not want a domain name. - $template->set('create', false); // Remove option to create new accounts from the wiki. - $template->set('useemail', false); // Disable the mail new password box. - } - - /** - * Set the domain this plugin is supposed to use when authenticating. - * - * @param $domain String: authentication domain. - * @public - */ - public function setDomain($domain) - { - $this->domain = $domain; - } - - /** - * Check to see if the specific domain is a valid domain. - * - * @param $domain String: authentication domain. - * @return bool - * @public - */ - public function validDomain($domain) - { - return true; - } - - /** - * This allows us to disable properties we don't want to allow users to modify - * @param $prop String: property to disallow - * @return bool - * @public - */ - public function allowPropChange($prop = '') - { - if ($prop == 'emailaddress') - return false; - - return true; - } - - /** - * When a user logs in, optionally fill in preferences and such. - * For instance, you might pull the email address or real name from the - * external user database. - * - * The User object is passed by reference so it can be modified; don't - * forget the & on your function declaration. - * - * @param User $user - * @public - */ - public function updateUser(&$user) - { - global $smf_settings, $smf_member_id; - - // No id, you must be unauthorized. - if ($smf_member_id == 0) - return false; - - $username = $this->fixUsername($user->getName()); - $request = $this->db_query(" - SELECT email_address, real_name - FROM $smf_settings[db_prefix]members - WHERE id_member = '{$smf_member_id}' - LIMIT 1"); - - while ($row = $this->db_fetch_assoc($request)) - { - $user->setRealName($row['real_name']); - $user->setEmail($row['email_address']); - - $this->setAdminGroup($user); - - $user->setOption('smf_last_update', time()); - $user->saveSettings(); - } - $this->db_free_result($request); - - return true; - } - - - /** - * Return true if the wiki should create a new local account automatically - * when asked to login a user who doesn't exist locally but does in the - * external auth database. - * - * If you don't automatically create accounts, you must still create - * accounts in some way. It's not possible to authenticate without - * a local account. - * - * This is just a question, and shouldn't perform any actions. - * - * @return bool - * @public - */ - public function autoCreate() - { - return true; - } - - /** - * Can users change their passwords? - * - * @return bool - */ - public function allowPasswordChange() - { - global $wgSMFLogin; - - // Only allow password change if not using auto login. - // Otherwise we would need a bunch of code to rewrite - // the SMF login cookie with the new password. - if (isset($wgSMFLogin) && $wgSMFLogin) - return false; - - return true; - } - - /** - * Update user information in the external authentication database. - * Return true if successful. - * - * @param $user User object. - * @return bool - * @public - */ - public function updateExternalDB($user) - { - return true; - } - - /** - * Check to see if external accounts can be created. - * Return true if external accounts can be created. - * @return bool - * @public - */ - public function canCreateAccounts() - { - return false; - } - - /** - * Add a user to the external authentication database. - * Return true if successful. - * - * @param User $user - only the name should be assumed valid at this point - * @param string $password - * @param string $email - * @param string $realname - * @return bool - * @public - */ - public function addUser($user, $password, $email='', $realname='') - { - return true; - } - - - /** - * Return true to prevent logins that don't authenticate here from being - * checked against the local database's password fields. - * - * This is just a question, and shouldn't perform any actions. - * - * @return bool - * @public - */ - public function strict() - { - return true; - } - - /** - * When creating a user account, optionally fill in preferences and such. - * For instance, you might pull the email address or real name from the - * external user database. - * - * The User object is passed by reference so it can be modified; don't - * forget the & on your function declaration. - * - * @param $user User object. - * @param $autocreate bool True if user is being autocreated on login - * @public - */ - public function initUser(&$user, $autocreate = false) - { - global $smf_settings, $smf_member_id; - - // No id, you must be unauthorized. - if ($smf_member_id == 0) - return false; - - // Check what time we last did this. - $last_update = $user->getOption('smf_last_update', 0); - if (!empty($last_update) && time() > $last_update + 900) - return true; - - $username = $this->fixUsername($user->getName()); - $request = $this->db_query(" - SELECT id_member, email_address, real_name - FROM $smf_settings[db_prefix]members - WHERE id_member = '{$smf_member_id}' - LIMIT 1"); - - while ($row = $this->db_fetch_assoc($request)) - { - $user->setRealName($row[real_name]); - $user->setEmail($row[email_address]); - - // Let wiki know that their email has been verified. - $user->mEmailAuthenticated = wfTimestampNow(); - - $this->setAdminGroup($user); - - $user->setOption('smf_last_update', time()); - $user->saveSettings(); - } - $this->db_free_result($request); - - return true; - } - - /** - * If you want to munge the case of an account name before the final - * check, now is your chance. - * - * @public - */ - public function getCanonicalName($username) - { - /** - * wiki converts username (john_doe -> John doe) - * then getCanonicalName is called - * user not in wiki database call userExists - * lastly call authenticate - */ - return $username; - } - - /** - * The wiki converts underscores to spaces. Attempt to work around this - * by checking for both cases. Hopefully we'll only get one match. - * Otherwise the first registered SMF account takes priority. - * - * @public - */ - public function fixUsername($username) - { - global $smf_settings, $smf_member_id; - static $fixed_name = ''; - - // No space no problem. - if(strpos($username, ' ') === false) - return $username; - - // We may have done this once already. - if (!empty($fixed_name)) - return $fixed_name; - - // Look for either case sorted by date. - $request = $this->db_query(" - SELECT member_name - FROM $smf_settings[db_prefix]members - WHERE member_name = '{$username}' - OR member_name = '" . strtr(strtr($username, array(' ' => '_', '[' => '=', ']' => '"', '|' => '&', '#' => '\\')), array('{' => '==', '}' => '""')) . "' - ORDER BY date_registered ASC - LIMIT 1"); - - list($user) = $this->db_fetch_row($request); - $this->db_free_result($request); - - // No result play it safe and return the original. - $fixed_name = $user; - return !isset($user) ? $username : $user; - } - - /** - * Check to see if the user is banned partially - * restricting their ability to post or login. - * - * @public - */ - public function isNotBanned($id_member) - { - global $smf_settings, $smf_member_id; - - // Perhaps we have it cached in the session. - if (empty($smf_settings['debug_wiki']) && isset($_SESSION['smf_iNB_t'], $_SESSION['smf_iNB']) && time() < ($_SESSION['smf_iNB_t'] + 900)) - return $_SESSION['smf_iNB'] ? true : false; - - $request = $this->db_query(" - SELECT id_ban - FROM $smf_settings[db_prefix]ban_items AS i - LEFT JOIN $smf_settings[db_prefix]ban_groups AS g - ON (i.id_ban_group = g.id_ban_group) - WHERE i.id_member = '{$id_member}' - AND (g.cannot_post = 1 OR g.cannot_login = 1)"); - - $banned = $this->db_num_rows($request); - $this->db_free_result($request); - - $_SESSION['smf_iNB_t'] = time(); - $_SESSION['smf_iNB'] = $banned ? false : true; - - return $_SESSION['smf_iNB']; - } - - /** - * Check to see if the user is able to login. - * - * @public - */ - public function canLogin() - { - global $wgSMFDenyGroupID, $user_settings; - - if (empty($smf_settings['debug_wiki']) && isset($_SESSION['smf_cL_t'], $_SESSION['smf_cL']) && time() < ($_SESSION['smf_cL_t'] + 900)) - return $_SESSION['smf_cL'] ? true : false; - - $_SESSION['smf_iNB_t'] = time(); - $_SESSION['smf_cL'] = true; - - if (!empty($wgSMFDenyGroupID) && array_intersect($user_settings['smf_groups'], $wgSMFDenyGroupID) != array()) - $_SESSION['smf_cL'] = false; - - return $_SESSION['smf_cL']; - } - - /** - * Check to see if the user should have sysop rights. - * Either they are an administrator or are in one - * of the define groups. - * - * To save database queries the fixed username is used. - * - * @public - */ - public function setAdminGroup(&$user) - { - global $wgSMFAdminGroupID, $user_settings; - static $already_done = false; - - // Loop prevention. - if ($already_done) - return; - $already_done = true; - - // Check if we did this already recently. - if (empty($smf_settings['debug_wiki']) && isset($_SESSION['smf_sAG']) && time() < ($_SESSION['smf_sAG'] + 900)) - return; - $_SESSION['smf_sAG'] = time(); - - // Administrator always get admin rights. - if (!in_array(1, $wgSMFAdminGroupID)) - $wgSMFAdminGroupID[] = 1; - - // Search through all groups, if match give them admin rights. - if (!empty($wgSMFAdminGroupID) && array_intersect($user_settings['smf_groups'], $wgSMFAdminGroupID) != array()) - { - if (!in_array("sysop", $user->getEffectiveGroups())) - $user->addGroup("sysop"); - - return; - } - - // No go! Make sure they are not a sysop. - if (in_array("sysop", $user->getEffectiveGroups())) - $user->removeGroup("sysop"); - return; - } - - - /** - * Check to see if the user is allowed to log in. - * Either they are an administrator or are in one - * of the define groups. - * - * @public - */ - public function isGroupAllowed($username, &$user) - { - global $wgSMFGroupID, $wgSMFDenyGroupID, $wgSMFSpecialGroups, $user_settings; - - // Check if we did this already recently. - if (empty($smf_settings['debug_wiki']) && isset($_SESSION['smf_iSA_t'], $_SESSION['smf_iSA']) && time() < ($_SESSION['smf_iSA_t'] + 900)) - return $_SESSION['smf_iSA']; - $_SESSION['smf_iSA_t'] = time(); - - // This allows us to wiki assign groups based on SMF member groups. - if (!empty($wgSMFSpecialGroups)) - { - // This is done for speed purposes when working with a large array. - $temp_groups = explode(',', $user_settings['additional_groups']); - - foreach ($wgSMFSpecialGroups as $smf_group => $wiki_group) - { - if (in_array($smf_group, $temp_groups) && !in_array($wiki_group, $user->getEffectiveGroups())) - { - $user->addGroup($wiki_group); - $group_added = true; - } - } - } - - // Do they happen to be in a deny group? - if (!empty($wgSMFDenyGroupID) && array_intersect($user_settings['smf_groups'], $wgSMFDenyGroupID) != array()) - $_SESSION['smf_iSA'] = false; - // This comes from the group add above. - elseif (!empty($group_added)) - $_SESSION['smf_iSA'] = true; - // No limitations. - elseif (empty($wgSMFGroupID)) - $_SESSION['smf_iSA'] = true; - // Search through all groups, if match give them admin rights. - elseif (!empty($wgSMFGroupID) && array_intersect($user_settings['smf_groups'], $wgSMFGroupID) != array()) - $_SESSION['smf_iSA'] = true; - else - // No go! - $_SESSION['smf_iSA'] = false; - - return $_SESSION['smf_iSA']; - } - - /** - * Connect to the database. Use the settings from smf. - * - * {@source} - * @return resource - */ - public function connect() - { - global $smf_settings, $smcFunc; - - $db_type = !empty($smf_settings['db_type']) && file_exists($smf_settings['sourcedir'] . '/Subs-Db-' . $smf_settings['db_type'] . '.php') ? $smf_settings['db_type'] : 'mysql'; - - if (!defined('SMF')) - define('SMF', 'WIKI'); - if (!isset($smcFunc)) - $smcFunc = array(); - - require_once($smf_settings['sourcedir'] . '/Subs-Db-' . $smf_settings['db_type'] . '.php'); - - $this->conn = smf_db_initiate($smf_settings['db_server'], $smf_settings['db_name'], $smf_settings['db_user'], $smf_settings['db_passwd'], $smf_settings['db_prefix']); - - // As of now, we don't suport anything other than UTF8 with SMF. - $smcFunc['db_query']('set_character_set', ' - SET NAMES UTF8', - array( - ) - ); - } - - /** - * Run the query and if applicable display the mysql error. - * - * @param string $query - * @return resource - */ - public function db_query($query) - { - global $smcFunc; - - $request = $smcFunc['db_query']('', $query, array(), $this->conn); - - if(!$request) - $this->db_error('Unable to view external table.'); - - return $request; - } - - /** - * Fetch the query with assoc. - * - * @param resource $request - * @return resource - */ - public function db_fetch_assoc($request) - { - global $smcFunc; - - return $smcFunc['db_fetch_assoc']($request); - } - - /** - * Fetch the query. - * - * @param resource $request - * @return resource - */ - public function db_fetch_row($request) - { - global $smcFunc; - - return $smcFunc['db_fetch_row']($request); - } - - /** - * Get the number or rows for the query. - * - * @param resource $request - * @return resource - */ - public function db_num_rows($request) - { - global $smcFunc; - - return $smcFunc['db_num_rows']($request); - } - - /** - * Free the query. - * - * @param resource $request - * @return resource - */ - public function db_free_result($request) - { - global $smcFunc; - - return $smcFunc['db_free_result']($request); - } - - /** - * Display an error when a error is found. - * - * @param string $message - * @access public - */ - public function db_error($message) - { - global $wgSMFDebug; - - echo $message . "

\n\n"; - - // Only if we are debugging. - if ($wgSMFDebug) - { - $db_type = !empty($smf_settings['db_type']) && file_exists($smf_settings['sourcedir'] . '/Subs-Db-' . $smf_settings['db_type'] . '.php') ? $smf_settings['db_type'] : 'mysql'; - - if ($db_type == 'postgresql') - echo 'PostgreSQL error: ', pg_last_error(), "
\n"; - else - echo 'MySQL error number: ', mysql_errno(), "
\n", 'MySQL error message: ', mysql_error(), "

\n\n"; - } - - exit; - } -} - -// Set the default options for a few settings we add. -// These are here as they do not need to be configurable. -$wgDefaultUserOptions['smf_member_id'] = 0; -$wgDefaultUserOptions['smf_last_update'] = 0; -$wgHooks['UserSaveOptions'][] = 'wfProfileSMFID'; - -// This prevents your SMF member ID from being lost when preferences are saved. -function wfProfileSMFID($user, &$saveOptions) -{ - global $ID_MEMBER; - - // Preserve our member id. - if (empty($saveOptions['smf_member_id']) && !empty($user->mOptionOverrides['smf_member_id'])) - $saveOptions['smf_member_id'] = $user->mOptionOverrides['smf_member_id']; - - // Still empty, maybe we can save the day. - if (empty($saveOptions['smf_member_id']) && !empty($ID_MEMBER)) - $saveOptions['smf_member_id'] = (int) $ID_MEMBER; - - // Note: We do not protect smf_last_update from being lost since we disabled - // changing emails means it would be lost and causes an error. This way the - // Auth will restore it on the next page load (ie right after the page save). - - return true; -} - -/** - * Because we don't fully load SMF, this doesn't exist, so handle this should an error occur.. - * - * @param string $query - * @return resource - */ -if (!function_exists('display_db_error')) -{ - function display_db_error() - { - global $wgSMFDebug; - - echo $message . "

\n\n"; - - // Only if we are debugging. - if ($wgSMFDebug) - { - $db_type = !empty($smf_settings['db_type']) && file_exists($smf_settings['sourcedir'] . '/Subs-Db-' . $smf_settings['db_type'] . '.php') ? $smf_settings['db_type'] : 'mysql'; - - if ($db_type == 'postgresql') - echo 'PostgreSQL error: ', pg_last_error(), "
\n"; - else - echo 'MySQL error number: ', mysql_errno(), "
\n", 'MySQL error message: ', mysql_error(), "

\n\n"; - } - - exit; - } -} \ No newline at end of file diff --git a/DatabaseProvider/MySQLi.php b/DatabaseProvider/MySQLi.php new file mode 100644 index 0000000..bf243fc --- /dev/null +++ b/DatabaseProvider/MySQLi.php @@ -0,0 +1,178 @@ +report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT; + + $this->db = @new mysqli($db_server, $db_user, $db_password, $db_name); + + if (!empty($this->db->connect_error)) + { + $this->DatabaseError($this->db->connect_error); + return false; + } + + $this->loaded = true; + } catch (\Exception $e) { + $this->DatabaseError($e); + return false; + } + + return true; + } + + /** + * Wrapper for MySQLi Query + * + * @param string $query The query we will perform against the database. + * @return resource Database resource + */ + public function query(string $query) + { + try { + $request = $this->db->query($query); + } catch (\mysqli_sql_exception $e) { + $this->DatabaseError($e); + return false; + } + + return $request; + } + + /** + * Wrapper for MySQLi fetch_assoc. + * + * @param resource $request Database resource + * @return array|bool The data returned. + */ + public function fetch_assoc($request) + { + if (empty($request)) + return false; + + try { + $row = $request->fetch_assoc(); + } catch (\mysqli_sql_exception $e) { + $this->FSDBError($e); + return false; + } + + return $row; + } + + /** + * Wrapper for MySQLi fetch_row. + * + * @param resource $request Database resource + * @return array|bool The data returned. + */ + public function fetch_row($request) + { + if (empty($request)) + return false; + + try { + $row = $request->fetch(); + } catch (\mysqli_sql_exception $e) { + $this->FSDBError($e); + return false; + } + + return $row; + } + + /** + * Wrapper for MySQLi num_rows. + * + * @param resource $request Database resource + * @return int|bool The number of rows, false if an error occured. + */ + public function fetch_num_rows($request) + { + if (empty($request)) + return 0; + + try { + return $request->num_rows; + } catch (\mysqli_sql_exception $e) { + $this->FSDBError($e); + return 0; + } + } + + /** + * Wrapper for MySQLi free_result. + * + * @param resource $request Database resource + * @return bool False if request contained nothing, otehrwise we assume it worked. + */ + public function free(&$request) + { + if (empty($request)) + return false; + + try { + $request->free_result(); + } catch (\mysqli_sql_exception $e) { + $this->FSDBError($e); + } + + return true; + } + + /** + * Wrapper for MySQLi real_escape_string. + * + * @param string $string The string we are escaping + * @return string The string escaped and ready for usage in the database. + */ + public function quote($string) + { + if (empty($string)) + return false; + + try { + $escaped = $this->db->real_escape_string($string); + } catch (\mysqli_sql_exception $e) { + $this->FSDBError($e); + + // better than nothing? + return (string) addslashes($string); + } + + return (string) $escaped; + } + +} \ No newline at end of file diff --git a/DatabaseProvider/PDO.php b/DatabaseProvider/PDO.php new file mode 100644 index 0000000..c8ad533 --- /dev/null +++ b/DatabaseProvider/PDO.php @@ -0,0 +1,202 @@ +db_type === 'postgresql' ? 'pgsql' : 'mysql'; + + $this->db = new PDO( + $type . ':host=' . $db_server . ';dbname=' . $db_name, + $db_user, + $db_user + ); + + if (!empty($this->db->connect_error)) + { + throw new ErrorException($this->db->connect_error); + return false; + } + + $this->loaded = true; + } catch (\Exception $e) { + $this->DatabaseError($e); + return false; + } + + return true; + } + + /** + * Wrapper for PDO Query + * + * @param string $query The query we will perform against the database. + * @return resource Database resource + */ + public function query(string $request) + { + try { + $statement = $this->db->prepare($query); + $request = $statement->execute(); + } catch (\PDOException $e) { + $this->DatabaseError($e); + return false; + } + + return $request; + + } + + /** + * Wrapper for PDO fetch using assoc. + * + * @param resource $request Database resource + * @return array|bool The data returned. + */ + public function fetch_assoc($request) + { + if (empty($request)) + return false; + + try { + $row = $request->fetch(PDO::FETCH_ASSOC); + } catch (\PDOException $e) { + $this->FSDBError($e); + return false; + } + + return $row; + } + + /** + * Wrapper for PDO fetch using row. + * + * @param resource $request Database resource + * @return array|bool The data returned. + */ + public function fetch_row($request) + { + if (empty($request)) + return false; + + try { + $row = $request->fetch(PDO::FETCH_NUM); + } catch (\PDOException $e) { + $this->FSDBError($e); + return false; + } + + return $row; + } + + /** + * Wrapper for PDO to fetch the number of rows. + * + * @param resource $request Database resource + * @return int|bool The number of rows, false if an error occured. + */ + public function fetch_num_rows($request) + { + if (empty($request)) + return 0; + + try { + /* + It should be noted that rowCount may not work on all databases. + In MySQL at least this works + References: https://www.php.net/manual/en/pdostatement.rowcount.php#example-1096 + References: https://stackoverflow.com/questions/11305230/alternative-for-mysql-num-rows-using-pdo#comment-32016656 + */ + if ($this->db_type === 'mysql') + return $request->rowCount(); + // Performance could be a issue here.. + else + { + $allRows = $request->fetchAll(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_COLUMN); + $rowCount = count($allRows); + unset($allRows); + + // Reset the counter and allow loops to work again. + $request->closeCursor(); + $request->execute(); + + return $rowCount; + } + } catch (\PDOException $e) { + $this->FSDBError($e); + return 0; + } + } + + /** + * Wrapper for PDO closeCursor. + * + * @param resource $request Database resource + * @return bool False if request contained nothing, otehrwise we assume it worked. + */ + public function free(&$request) + { + if (empty($request)) + return false; + + try { + $request->closeCursor(); + } catch (\PDOException $e) { + $this->FSDBError($e); + } + + return true; + } + + /** + * Wrapper for PDO quote. + * + * @param string $string The string we are escaping + * @return string The string escaped and ready for usage in the database. + */ + public function quote($string) + { + if (empty($string)) + return false; + + try { + $escaped = $this->db->quote($string); + } catch (\PDOException $e) { + $this->FSDBError($e); + + // better than nothing? + return (string) addslashes($string); + } + + return (string) $escaped; + } +} \ No newline at end of file diff --git a/DatabaseProvider/base.php b/DatabaseProvider/base.php new file mode 100644 index 0000000..08a8695 --- /dev/null +++ b/DatabaseProvider/base.php @@ -0,0 +1,81 @@ +MWlogger = &$MWlogger; + $this->db_type = $db_type === 'postgresql' ? 'postgresql' : 'mysql'; + } + + /** + * Checks if we have loaded and connected to a database session for our forum software. + * + * @return bool True if connected, otherwise false. + */ + public function isLoaded() + { + return $this->loaded; + } + + /** + * The database type. + * + * @return string The database type. + */ + public function getDbType() + { + return $this->db_type; + } + + /** + * Database error wrapper. Sends errors to the MediaWiki logger. + * + * @param Exception|string $error The error from the database, typically a exception object. + * @return void No return is generated. + */ + protected function DatabaseError($error) + { + if (is_object($error)) + $this->MWlogger->debug( + 'Database Error ({FSDBCODE}): {FSDBERROR}', + array( + 'FSDBCODE' => $error->getCode(), + 'FSDBERROR' => $error->getMessage() + )); + else + $this->MWlogger->debug($error); + } +} \ No newline at end of file diff --git a/ForumAuthManager.php b/ForumAuthManager.php new file mode 100644 index 0000000..44f4d1d --- /dev/null +++ b/ForumAuthManager.php @@ -0,0 +1,64 @@ +setOK(false); + return $rest; + } + + /* + * This one disables any other properties we need to block + */ + public function providerAllowsPropertyChange( $property ) + { + if (in_array($property, array( + 'realname', + 'emailaddress' + ))) + return false; + return true; + } +} \ No newline at end of file diff --git a/ForumProvider/base.php b/ForumProvider/base.php new file mode 100644 index 0000000..31481c6 --- /dev/null +++ b/ForumProvider/base.php @@ -0,0 +1,206 @@ +MWlogger = &$MWlogger; + $this->db = &$db; + $this->ForumSettings = &$ForumSettings; + } + + /** + * Validate that the configuration file exists and is readable. + * + * @param string $basepath The base path to the forum configuraiton file. The forum handler will load the appropriate configuraiton file. + * @return bool True if its valid, false otherwise. + */ + public function configurationFileIsValid(string $basepath) + { + return false; + } + + /** + * Reads the configuration file and loads the data into $ForumSetings for use later. + * + * @param string $basepath The base path to the forum configuraiton file. The forum handler will load the appropriate configuraiton file. + * @return void|bool Null if we loaded up data, false if it failed. + */ + public function readConfigurationFile(string $basepath) + { + return false; + } + + /** + * Determines if the cookie for the forum software exists. + * + * @return bool True if the forum software cookie exists, false otherwise. + */ + public function cookieExists() + { + return false; + } + + /** + * Decodes the forum software cookie returning the id and password. + * + * @return array The ID and password. + */ + public function decodeCookie() + { + return array('id' => 0, 'password' => ''); + } + + /* + * Figure out the URL that we need to send the user to in order to perform the requested action. + * The forum software should process the action and then return to MediaWiki. + * + * @param string $action The action that MediaWiki took under its special page. + * @param string $wiki_url The url we should return to once we have completed the action from the forum. + * @param bool $do_return If we should return to the wiki or not. + * @return string $forum_url The url to the forum we need to go do. + */ + public function getRedirectURL(string $action, string $wiki_url, bool $do_return = false) + { + // The base. + $forum_url = + $this->ForumSettings['boardurl'] + . '/index.php?action=' . $action; + + return $forum_url; + } + + /* + * Validate that the cookie has a valid password for the user. This should ensure + * that forged cookies or if a password has changed that the cookie rejects the login. + * + * @param array $user The user data. + * @param array $cookie The cookie data. + * @return bool True if the cookie is valid, false otherwise. + */ + public function cookiePasswordIsValid(array $user, array $cookie) + { + return false; + } + + /* + * Retrieves the member ID as defined by the forum software. Typically this is the same as the ID provided + * by the cookie but may differ depending on forum softwares. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + depend on the data from getForumMember having existed due to caching. + * @return int The ID of the member from the forum software. + */ + public function getForumMember(int $id_member) + { + return array(); + } + + /* + * Retrieves the member ID as defined by the forum software. Typically this is the same as the ID provided + * by the cookie but may differ depending on forum softwares. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + depend on the data from getForumMember having existed due to caching. + * @return array All data from the forum database. + */ + public function getMemberID(array $member) + { + return 0; + } + + /* + * Retrieves the member display name as defined by the forum software. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + depend on the data from getForumMember having existed due to caching. + * @return string The member display name. + */ + public function getMemberName(array $member) + { + return 'Guest'; + } + + /* + * Retrieves the member groups as defined by the forum software. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + depend on the data from getForumMember having existed due to caching. + * @return array An array of intergers of the forum group ids this member is apart of. + */ + public function getMemberGroups(array $member) + { + return array(); + } + + /* + * Retrieves the member email address as defined by the forum software. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + depend on the data from getForumMember having existed due to caching. + * @return string The member email address. + */ + public function getMemberEmailAddress(array $member) + { + return 'guest@example.com'; + } + + /* + * Retrieves the member login name as defined by the forum software. Typically this is the same as the ID provided + * by the cookie but may differ depending on forum softwares. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + depend on the data from getForumMember having existed due to caching. + * @return string The member login name. + */ + public function getMemberRealName(array $member) + { + return 'Guest'; + } + + /* + * Check if the member is banned in the forum software. If so let the SSO know to prevent them from + * logging into the wiki. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + depend on the data from getForumMember having existed due to caching. + * @return bool True if banned, false otherwise. + */ + public function checkBans(array $member) + { + return false; + } +} \ No newline at end of file diff --git a/ForumProvider/elk1.0.php b/ForumProvider/elk1.0.php new file mode 100644 index 0000000..c14a536 --- /dev/null +++ b/ForumProvider/elk1.0.php @@ -0,0 +1,94 @@ +ForumSettings['cookiename']], true); + } + + /* + * Figure out the URL that we need to send the user to in order to perform the requested action. + * The forum software should process the action and then return to MediaWiki. + * + * @param string $action The action that MediaWiki took under its special page. + * @param string $wiki_url The url we should return to once we have completed the action from the forum. + * @param bool $do_return If we should return to the wiki or not. + * @return string $forum_url The url to the forum we need to go do. + */ + public function getRedirectURL(string $action, string $wiki_url, bool $do_return = false) + { + $forum_action = $this->$validRedirectActions[$action]; + + $forum_url = + $this->ForumSettings['boardurl'] + . '/index.php?action=' . $forum_action; + + return $forum_url; + } + + /* + * Validate that the cookie has a valid password for the user. This should ensure + * that forged cookies or if a password has changed that the cookie rejects the login. + * + * @param array $user The user data. + * @param array $cookie The cookie data. + * @return bool True if the cookie is valid, false otherwise. + */ + public function cookiePasswordIsValid(array $user, array $cookie) + { + return $cookie['password'] === hash('sha256', $user['passwd'] . $user['password_salt']); + } + + /* + * Check if the member is banned in the forum software. If so let the SSO know to prevent them from + * logging into the wiki. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + depend on the data from getForumMember having existed due to caching. + * @return bool True if banned, false otherwise. + */ + public function checkBans(array $member) + { + $banned = $this->__check_basic_ban((int) $member['id_member']); + return false; + } + + /* + * Elk does not need to use the legacy conversion for the MediaWiki SMF_Auth.php + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + * depend on the data from getForumMember having existed due to caching. + * @param object $wikiuser The wiki user object provided for updating options. Note while getOption/setOption + * are deprecated, we still use them here as this legacy option should not be required in the future. + * @return bool True if we had to convert the setting and update the user, false otherwise. + */ + public function legacyUpdateWikiUser(array $member, array $wikiUser) + { + return false; + } +} \ No newline at end of file diff --git a/ForumProvider/elk1.1.php b/ForumProvider/elk1.1.php new file mode 100644 index 0000000..bf760bb --- /dev/null +++ b/ForumProvider/elk1.1.php @@ -0,0 +1,25 @@ + 'register', + 'userlogin' => 'login', + 'userlogout' => 'logout' + ); + + /** + * Validate that the configuration file exists and is readable. + * + * @param string $basepath The base path to the forum configuraiton file. The forum handler will load the appropriate configuraiton file. + * @return bool True if its valid, false otherwise. + */ + public function configurationFileIsValid(string $basepath) + { + return !empty($basepath) && is_readable($basepath . '/Settings.php'); + } + + /** + * Reads the configuration file and loads the data into $ForumSetings for use later.. + * + * @param string $basepath The base path to the forum configuraiton file. The forum handler will load the appropriate configuraiton file. + * @return void|bool Null if we loaded up data, false if it failed. + */ + public function readConfigurationFile(string $basepath) + { + foreach ($this->settingsFileVariables as $key) + global $$key; + + require ($basepath . '/Settings.php'); + + // Put these away for later. + foreach ($this->settingsFileVariables as $key) + $this->ForumSettings[$key] = !empty($GLOBALS[$key]) ? $GLOBALS[$key] : null; + } + + /* + * A compatiblity layer for Auth_SMF.php extension settings. + * + * @return void No return is expected. + */ + public function compatAuthSMF() + { + global $wgSMFPath, $wgSMFDenyGroupID, $wgSMFGroupID, $wgSMFAdminGroupID, $wgSMFSpecialGroups, $wgFSPNameStyle, $wgFSPEnableBanCheck; + + $this->MWlogger->debug('Detected SMF_Auth settings, loading compatibilty layer.'); + + $this->ForumSettings['path'] = isset($wgSMFPath) ? $wgSMFPath : '../forum'; + + // We only need to load settings that where in Auth_SMF, loadFSSettings handles the standard settings. + foreach (array( + 'LoginDeniedGroups' => 'wgSMFDenyGroupID', + 'LoginAllowedGroups' => 'wgSMFGroupID', + 'AdminGroups' => 'wgSMFAdminGroupID', + 'SuperAdminGroups' => 'wgSMFAdminGroupID', + 'InterfaceGroups' => 'wgSMFAdminGroupID', + 'SpecialGroups' => 'wgSMFSpecialGroups', + ) as $key => $value) + $this->ForumSettings[$key] = !empty($$value) ? $$value : $this->ForumSettings[$key]; + + // Set the login style to SMF + if (empty($this->ForumSettings['NameStyle'])) + $this->ForumSettings['NameStyle'] = 'smf'; + + // The old Auth_SMF plugin did ban checks, enable it unless specified. + if (!isset($this->ForumSettings['EnableBanCheck'])) + $this->ForumSettings['EnableBanCheck'] = true; + } + + /** + * Determines if the cookie for the forum software exists. + * + * @return bool True if the forum software cookie exists, false otherwise. + */ + public function cookieExists() + { + return !empty($_COOKIE[$this->ForumSettings['cookiename']]); + } + + /** + * Decodes the forum software cookie returning the id and password. + * + * @return array The ID and password. + */ + public function decodeCookie() + { + return (array) unserialize($_COOKIE[$this->ForumSettings['cookiename']]); + } + + /* + * Figure out the URL that we need to send the user to in order to perform the requested action. + * The forum software should process the action and then return to MediaWiki. + * + * @param string $action The action that MediaWiki took under its special page. + * @param string $wiki_url The url we should return to once we have completed the action from the forum. + * @param bool $do_return If we should return to the wiki or not. + * @return string $forum_url The url to the forum we need to go do. + */ + public function getRedirectURL(string $action, string $wiki_url, bool $do_return = false) + { + $forum_action = $this->$validRedirectActions[$action]; + + $forum_url = + $this->ForumSettings['boardurl'] + . '/index.php?action=' . $forum_action + . ';return_hash=' . hash_hmac('sha1', $wiki_url, $this->ForumSettings['image_proxy_secret']) + . ';return_to=' . base64_encode($wiki_url); + + return (string) $forum_url; + } + + /* + * Validate that the cookie has a valid password for the user. This should ensure + * that forged cookies or if a password has changed that the cookie rejects the login. + * + * @param array $user The user data. + * @param array $cookie The cookie data. + * @return bool True if the cookie is valid, false otherwise. + */ + public function cookiePasswordIsValid(array $user, array $cookie) + { + // 2.0.16 added a hash secret for cookies, preventing forgeries of the cookie, use it if enabled. + if (!empty($this->ForumSettings['auth_secret']) && empty($this->ForumSettings['cookie_no_auth_secret'])) + return $cookie['password'] === hash_hmac('sha1', sha1($user['passwd'] . $user['password_salt']), $this->ForumSettings['auth_secret']); + else + return $cookie['password'] === sha1($user['passwd'] . $user['password_salt']); + } + + /* + * Retrieves the forum member data as defined by the forum. This typically will use a database + * session and query the database for the required information. The data is returned to the + * main handler to be cached and used. + * + * @param int $id_member The id of the member to load from the database. This is determiend by the cookie. + * @return array All data from the forum database. + */ + public function getForumMember(int $id_member) + { + $result = $this->db->query(' + SELECT + id_member, + member_name, email_address, real_name, passwd, password_salt, + id_group, id_post_group, additional_groups, + member_ip, member_ip2 + FROM ' . $this->ForumSettings['db_prefix'] . 'members + WHERE + id_member = ' . (int) $id_member . ' + AND is_activated = 1 + LIMIT 1'); + $member = $this->db->fetch_assoc($result); + $this->db->free($result); + + return (array) $member; + } + + /* + * Retrieves the member ID as defined by the forum software. Typically this is the same as the ID provided + * by the cookie but may differ depending on forum softwares. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + depend on the data from getForumMember having existed due to caching. + * @return int The ID of the member from the forum software. + */ + public function getMemberID(array $member) + { + return (int) $member['id_member']; + } + + /* + * Retrieves the member display name as defined by the forum software. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + depend on the data from getForumMember having existed due to caching. + * @return string The member display name. + */ + public function getMemberName(array $member) + { + return (string) $member['member_name']; + } + + /* + * Retrieves the member groups as defined by the forum software. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + depend on the data from getForumMember having existed due to caching. + * @return array An array of intergers of the forum group ids this member is apart of. + */ + public function getMemberGroups(array $member) + { + $groups = array( + (int) $member['id_group'], + (int) $member['id_post_group'] + ); + + if (!empty($member['additional_groups'])) + $groups += explode(',', $member['additional_groups']); + + return $groups; + } + + /* + * Retrieves the member email address as defined by the forum software. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + depend on the data from getForumMember having existed due to caching. + * @return string The member email address. + */ + public function getMemberEmailAddress(array $member) + { + return (string) $member['email_address']; + } + + /* + * Retrieves the member login name as defined by the forum software. Typically this is the same as the ID provided + * by the cookie but may differ depending on forum softwares. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + depend on the data from getForumMember having existed due to caching. + * @return string The member login name. + */ + public function getMemberRealName(array $member) + { + return (string) $member['real_name']; + } + + /* + * SMF Auth used a different option to track for account take overs. This converts it to the method used + * by this extension. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + * depend on the data from getForumMember having existed due to caching. + * @param object $wikiuser The wiki user object provided for updating options. Note while getOption/setOption + * are deprecated, we still use them here as this legacy option should not be required in the future. + * @return bool True if we had to convert the setting and update the user, false otherwise. + */ + public function legacyUpdateWikiUser(array $member, object $wikiUser) + { + // Convert the smf_member_id over? + if ($this->ForumSettings['NameStyle'] == 'smf' && $wikiUser->getOption('forum_member_id', 0) == 0 && $wikiUser->getOption('smf_member_id', 0) != 0) + { + $this->MWlogger->debug('SMF Auth conversion to FS Provider. "{OLD}" vs "{NEW}"', array( + 'OLD' => $wikiUser->getOption('forum_member_id', 0), + 'NEW' => $wikiUser->getOption('smf_member_id', 0), + )); + + $wikiUser->setOption('forum_member_id', $member['id_member']); + $wikiUser->setOption('smf_member_id', 0); + return true; + } + + return false; + } + + /* + * Check if the member is banned in the forum software. If so let the SSO know to prevent them from + * logging into the wiki. + * + * We first check to see if the is_activated is >= 10, which in SMF indicates they are banned. + * We then check the bans table to see if their is another ban to validate against incase their profile does not reflect the ban. + * We then check to see if their email. This is used incase the email has changed but the ban rule is not updated. + * We then check to see if their IP matches. This is used to ensure IP bans activate as expected. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + depend on the data from getForumMember having existed due to caching. + * @return bool True if banned, false otherwise. + */ + public function checkBans(array $member) + { + $banned = isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0; + + if (empty($banned)) + $banned = $this->__check_basic_ban((int) $member['id_member']); + + if (empty($banned)) + $banned = $this->__check_email_ban((string) $member['email_address']); + + if (empty($banned)) + { + $ips = array( + $member['ip'], + $member['ip2'], + $_SERVER['REMOTE_ADDR'], + ); + + // SMF 2.0 only supports IPv4. + foreach ($ips as $ip) + if (preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $ip, $ip_parts) == 1) + { + $banned = $this->__check_ip_ban($ip, $ip_parts); + + if ($banned) + continue; + } + } + + return $banned; + } + + /* + * Check if the member is banned in the forum software by checking for the basic ban member. + * + * @param int $id_member The id of the member provided by the forum software. + * @return bool True if banned, false otherwise. + */ + protected function __check_basic_ban(int $id_member) + { + $request = $this->db->query(' + SELECT id_ban + FROM ' . $this->ForumSettings['db_prefix'] . 'ban_items AS bi + LEFT JOIN ' . $this->ForumSettings['db_prefix'] . 'ban_groups AS bg + ON (bi.id_ban_group = bg.id_ban_group) + WHERE bi.id_member = ' . $id_member . ' + AND (bg.cannot_post = 1 OR bg.cannot_login = 1)'); + + $banned = (int) $this->db->fetch_assoc($request); + $this->db->free($request); + + return $banned !== 0; + } + + + /* + * Check if the member is banned in the forum software by checking for the email address. + * + * @param string $email_address The forum members email address. + * @return bool True if banned, false otherwise. + */ + protected function __check_email_ban(string $email_address) + { + $request = $this->db->query(' + SELECT id_ban + FROM ' . $this->ForumSettings['db_prefix'] . 'ban_items AS bi + LEFT JOIN ' . $this->ForumSettings['db_prefix'] . 'ban_groups AS bg + ON (bi.id_ban_group = bg.id_ban_group) + WHERE ' . $this->db->quote($email_address) . ' LIKE bi.email_address + AND (bg.cannot_post = 1 OR bg.cannot_login = 1)'); + + $banned = (int) $this->db->fetch_assoc($request); + $this->db->free($request); + + return $banned !== 0; + } + + /* + * Check if the member is banned in the forum software by checking for the IP address. + * + * @param string $ip The forum members email address. + * @param array $ip_parts The IP exploded. + * @return bool True if banned, false otherwise. + */ + protected function __check_ip_ban(string $ip, array $ip_parts) + { + $request = $this->db->query(' + SELECT id_ban + FROM ' . $this->ForumSettings['db_prefix'] . 'ban_items AS bi + LEFT JOIN ' . $this->ForumSettings['db_prefix'] . 'ban_groups AS bg + ON (bi.id_ban_group = bg.id_ban_group) + WHERE ((' . (int) $ip_parts[1] . ' BETWEEN bi.ip_low1 AND bi.ip_high1) + AND (' . (int) $ip_parts[2] . ' BETWEEN bi.ip_low2 AND bi.ip_high2) + AND (' . (int) $ip_parts[3] . ' BETWEEN bi.ip_low3 AND bi.ip_high3) + AND (' . (int) $ip_parts[4] . ' BETWEEN bi.ip_low4 AND bi.ip_high4)) + AND (bg.cannot_post = 1 OR bg.cannot_login = 1)'); + + $banned = (int) $this->db->fetch_assoc($request); + $this->db->free($request); + + return $banned !== 0; + } +} \ No newline at end of file diff --git a/ForumProvider/smf2.1.php b/ForumProvider/smf2.1.php new file mode 100644 index 0000000..212e984 --- /dev/null +++ b/ForumProvider/smf2.1.php @@ -0,0 +1,147 @@ +ForumSettings['cookiename']], true); + } + + /* + * Figure out the URL that we need to send the user to in order to perform the requested action. + * The forum software should process the action and then return to MediaWiki. + */ + public function getRedirectURL(string $action, string $wiki_url, bool $do_return = false) + { + $forum_action = $this->$validRedirectActions[$action]; + + $forum_url = + $this->ForumSettings['boardurl'] + . '/index.php?action=' . $forum_action + . ';return_hash=' . hash_hmac('sha1', $wiki_url, $this->ForumSettings['auth_secret']) + . ';return_to=' . base64_encode($wiki_url); + + return $forum_url; + } + + /* + * Validate that the cookie has a valid password for the user. This should ensure + * that forged cookies or if a password has changed that the cookie rejects the login. + * + * @param array $user The user data. + * @param array $cookie The cookie data. + * @return bool True if the cookie is valid, false otherwise. + */ + public function cookiePasswordIsValid(array $user, array $cookie) + { + if (empty($user) || empty($cookie)) + return false; + + if (!empty($this->ForumSettings['auth_secret']) && empty($this->ForumSettings['cookie_no_auth_secret'])) + return hash_equals(hash_hmac('sha512', $user['passwd'], $this->ForumSettings['auth_secret'] . $user['password_salt']), $cookie['password']); + else + return $cookie['password'] === hash('sha512', $user['passwd'] . $user['password_salt']); + } + + /* + * Check if the member is banned in the forum software. If so let the SSO know to prevent them from + * logging into the wiki. + * + * We first check to see if the is_activated is >= 10, which in SMF indicates they are banned. + * We then check the bans table to see if their is another ban to validate against incase their profile does not reflect the ban. + * We then check to see if their email. This is used incase the email has changed but the ban rule is not updated. + * We then check to see if their IP matches. This is used to ensure IP bans activate as expected. + * + * @param array $member The set of forum member data previsouly returned by getForumMember. Do not + depend on the data from getForumMember having existed due to caching. + * @return bool True if banned, false otherwise. + */ + public function checkBans(array $member) + { + $banned = isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0; + + if (empty($banned)) + $banned = $this->__check_basic_ban((int) $member['id_member']); + + if (empty($banned)) + $banned = $this->__check_email_ban((string) $member['email_address']); + + if (empty($banned)) + { + $ips = array( + $member['member_ip'], + $member['member_ip2'], + $_SERVER['REMOTE_ADDR'], + ); + + // SMF 2.0 only supports IPv4. + foreach ($ips as $ip) + { + $banned = $this->__check_ip_ban($ip, []); + + if ($banned) + continue; + } + } + + return $banned; + } + + /* + * Check if the member is banned in the forum software by checking for the IP address. + * + * @param string $ip The forum members email address. + * @return bool True if banned, false otherwise. + */ + protected function __check_ip_ban(string $ip, array $void) + { + $ip_bin = bin2hex(inet_pton($ip)); + + $sql = ' + SELECT id_ban + FROM ' . $this->ForumSettings['db_prefix'] . 'ban_items AS bi + LEFT JOIN ' . $this->ForumSettings['db_prefix'] . 'ban_groups AS bg + ON (bi.id_ban_group = bg.id_ban_group)'; + + // Postgresql uses ::inet + if ($this->db->getDbType() === 'postgresql') + $sql .= ' + WHERE (' . (string) $this->db->quote($ip_bin) . '::inet) BETWEEN bi.ip_low AND bi.ip_high)'; + else + $sql .= ' + WHERE (unhex(' . (string) $this->db->quote($ip_bin) . ') BETWEEN bi.ip_low AND bi.ip_high)'; + + $sql .= ' + AND (bg.cannot_post = 1 OR bg.cannot_login = 1)'; + + $request = $this->db->query($sql); + $banned = (int) $this->db->fetch_assoc($request); + $this->db->free($request); + + return $banned !== 0; + } +} \ No newline at end of file diff --git a/ForumSsoProvider.php b/ForumSsoProvider.php new file mode 100644 index 0000000..b017f03 --- /dev/null +++ b/ForumSsoProvider.php @@ -0,0 +1,732 @@ +MWlogger = \MediaWiki\Logger\LoggerFactory::getInstance('ForumSessionProvider'); + $this->MWlogger->debug('Constructor initialized.'); + + // Set our software up. + $this->ForumSoftware = !empty($wgSMFLogin) && empty($wgFSPSoftware) ? 'smf2.0' : (!empty($wgFSPSoftware) ? $wgFSPSoftware : null); + + // Load our settings. + $this->wikiScriptPath = $wgScriptPath; + $this->loadFSSettings(); + + // Load up the correct forum software provider. + $forumClass = 'ForumSoftwareProvider' . str_replace('.', '', $this->ForumSoftware); + $this->fs = new $forumClass($this->MWlogger, $this->db, $this->ForumSettings); + + // Is this a legacy authentication plugin?. + if (!empty($wgSMFLogin) && method_exists($this->fs, 'compatLegacy')) + $this->fs->compatLegacy(); + + // Make sure we can find the settings file. + if ($this->fs->configurationFileIsValid($this->ForumSettings['path'])) + { + $this->MWlogger->debug('Found Configuration File, attempting to loading.'); + + // Read the Settings file in, use this layer to adjust what we need to bring in. + $this->fs->readConfigurationFile($this->ForumSettings['path']); + + // Read the cookie + $this->decodeCookie(); + + // If we have a valid ID, lets connect to the database. + if (!empty($this->ForumCookie['id']) && is_integer($this->ForumCookie['id'])) + { + $this->MWlogger->debug('User detected, attempting to load the database.'); + + $this->setupDatabaseProvider(); + } + else + $this->MWlogger->debug('No User detected, fall through to MediaWiki.'); + } + else + { + $this->MWlogger->debug('Configuration File missing or not readable. Tried to load at {path}', array('path' => $this->ForumSettings['path'])); + $this->MWlogger->warning('Forum Software Integraiton invalid.'); + } + } + + /** + * MediaWiki will call t his when loading a special page. We only need to grab a few pages + * and redirect them to the forum for handling. These are login, logout and registering a new account. + * This just returns to the objecct via $wgForumSessionProviderInstance and calls doRedirect; + * + * @param object $special The special page called. + * @param string|null $subPage Subpage string, or null if no subpage was specified + * @hook MediaWiki\SpecialPage\Hook\SpecialPageBeforeExecuteHook::onSpecialPageBeforeExecute + * @return void If this matches, we issue a redirect, otherwise we return nothing. + */ + public static function onSpecialPageBeforeExecute($special, $subPage) + { + global $wgForumSessionProviderInstance; + + // The case of some of these isn't always consistent with what shows up in the url. + $special_action = strtolower($special->getName()); + + // If this is a valid action, let the redirector know. + if (in_array($special_action, array('createaccount', 'userlogin', 'userlogout'))) + $wgForumSessionProviderInstance->doRedirect($special_action, true); + } + + /** + * Actually do the redirect. We setup where we are at in the wiki and then ask the forum software + * to handle redirecting us. The forum software is responsible for handling the action and returning + * to the proper location in MediaWiki. + * + * @param string $action The action we are calling. + * @param bool $do_return if we should return or not. + * @return void We will be doing a redirect and exiting execution here. + */ + public function doRedirect(string $action, bool $do_return = false) + { + global $wgScriptPath; + + // The wiki URL. + $page = !empty($_GET['returnto']) ? '?title=' . $_GET['returnto'] . '&' : '?'; + $wiki_url = 'http://' . $_SERVER['SERVER_NAME'] . $wgScriptPath . '/index.php' . $page . 'board=redirect'; + + // Send this to the forum handler to give us the proper redirect url. + $redirect_url = $this->fs->getRedirectURL($action, $wiki_url, $do_return); + + // Redirect and leave this. + header ('Location: ' . $redirect_url); + exit; + } + + /** + * Sets up the the session for MediaWiki and returns it. + * MediaWiki will call this directly when it is ready to load up the user. + * This will validate the user is logged into the forum, perform any updates and ban checks, + * then let MediaWiki know this session is valid. + * + * @param WebRequest The request information provided by MediaWIki. + * @return SessionInfo|null A valid session handler is returned if the user is logged in, otherwise null. + */ + public function provideSessionInfo(WebRequest $request) + { + // Can't do this without a database connection, they are a guest now. + if (empty($this->db) || empty($this->db->isLoaded())) + { + $this->MWlogger->debug('Unable to provide session, database not loaded.'); + return null; + } + else + $this->MWlogger->debug('Database loaded, attempting to load forum member.'); + + // Fetch the user. + $this->ForumMember = $this->getForumMember($request); + + // Can't find this member. + if (empty($this->ForumMember)) + { + $this->MWlogger->debug('Member id, {FSID}, not found in forum database', array('FSID' => $this->ForumCookie['id'])); + return null; + } + else + $this->MWlogger->debug('Forum member found, verifying cookie of {FSID}', array('FSID' => $this->ForumCookie['id'])); + + // Password not valid? + if (!$this->fs->cookiePasswordIsValid($this->ForumMember, $this->ForumCookie)) + { + $this->MWlogger->debug('Member ID, {FSID}, failed to validate password under {USERIP}', array( + 'FSID' => $this->ForumCookie['id'], + 'USERIP' => $_SERVER['REMOTE_ADDR'], + )); + return null; + } + else + $this->MWlogger->debug('Member found and verified, verifying access.'); + + // Cleanup the username. + $this->ForumMemberNameCleaned = $this->cleanupUserName($this->fs->getMemberName($this->ForumMember)); + + // Invalid name? + if (is_null($this->ForumMemberNameCleaned)) + { + $this->MWlogger->debug('Invalid username, aborting integraiton.'); + return null; + } + + // Get all of our Forum Software groups. + $this->ForumMemberGroups = $this->fs->getMemberGroups($this->ForumMember); + + // Try to access this user. + $this->MWlogger->debug('Attempting to locate a valid user in MediaWiki or create one if it does not exist'); + $this->wikiUserInfo = \MediaWiki\Session\UserInfo::newFromName($this->ForumMemberNameCleaned, true); + $this->wikiMember = $this->wikiUserInfo->getUser(); + + // If they are not logged in or the username doesnt match. + if (!($this->wikiMember->isLoggedIn() && $this->wikiMember->getName() === $this->ForumMemberNameCleaned)) + { + $this->MWlogger->debug('Attempting to login a mediawiki user, if the user does not exist, this fails silently.'); + + $this->wikiMember->setId($this->wikiMember->idFromName($this->ForumMemberNameCleaned)); + + // The user doesn't exist yet in the wiki? Create them. + if ($this->wikiMember->getID() === 0) + { + $this->MWlogger->debug('User does not exist, attemtping to create it.'); + $this->createWikiUser(); + } + } + + // Make sure if we have a id match, its valid. + if ($this->getUserOption('forum_member_id') !== 0 && $this->getUserOption('forum_member_id') !== $this->ForumCookie['id']) + { + $this->MWlogger->debug('Member ID, {FSID}, failed to match forum provider check under {USERIP}', array( + 'FSID' => $this->ForumCookie['id'], + 'USERIP' => $_SERVER['REMOTE_ADDR'], + )); + return null; + } + else + $this->MWlogger->debug('Forum Provider check validated.'); + + // Check the ban status here. + if ($this->memberIsBannedOnForum()) + { + $this->MWlogger->debug('Member was matched as banned.'); + return null; + } + + // Configure all of our groups, but only every 15 minutes. + if (time() > ((int) $this->getUserOption('forum_last_update_groups') + $this->update_groups_interval)) + $this->updateWikiUserGroups(); + + // If any user data has changed, go ahead and update it now. + $this->updateWikiUser(); + + // Denied Login? + if ( + !empty($this->ForumSettings['LoginDeniedGroups']) + && is_array($this->ForumSettings['LoginDeniedGroups']) + && array_intersect($this->ForumSettings['LoginDeniedGroups'], $this->ForumMemberGroups) !== array() + ) + { + $this->MWlogger->debug('Member was found in Login Deny Groups, rejected...'); + return null; + } + + // Not apart of a login group? + $tempGroups = (array) $this->ForumSettings['LoginAllowedGroups']; + $tempGroups += (array) $this->ForumSettings['AdminGroups']; + if (!empty($this->ForumSettings['LoginAllowedGroups']) && array_intersect($tempGroups, $this->ForumMemberGroups) === array()) + { + $this->MWlogger->debug('Member is not apart of any login groups...'); + return null; + } + + $this->MWlogger->debug('Everything is valid, returning valid session for wiki...'); + + // This was in the original code and sessionCookieName is not defined anywhere. + if ($this->sessionCookieName === null) + { + $id = $this->hashToSessionId($this->ForumMemberNameCleaned); + $persisted = false; + $forceUse = true; + } + else + { + $id = $this->getSessionIdFromCookie($request); + $persisted = $id !== null; + $forceUse = false; + } + + // Stand up a new session for MediaWiki. + return new \MediaWiki\Session\SessionInfo(\MediaWiki\Session\SessionInfo::MAX_PRIORITY, array( + 'provider' => $this, + 'id' => $id, + 'userInfo' => $this->wikiUserInfo, + 'persisted' => $persisted, + 'forceUse' => $forceUse, + )); + } + + /** + * Load up all MediaWiki settings for the Forum Session Provider extension. + * This will simply passt them into a localized array for processing later. + * + * @return void No return is expected. + */ + private function loadFSSettings() + { + global $wgFSPPath, $wgFSPDenyGroups, $wgFSPAllowGroups, $wgFSPAdminGroups, $wgFSPSuperGroups, $wgFSPInterfaceGroups, $wgFSPSpecialGroups, $wgFSPNameStyle, $wgFSPEnableBanCheck; + + $this->MWlogger->debug('Loading Forum System Settings.'); + + // Some standard settings and if they do not exist, provide a default. + $this->ForumSettings['path'] = isset($wgFSPPath) ? $wgFSPPath : '../forum'; + $this->ForumSettings['NameStyle'] = !empty($wgFSPNameStyle) ? strtolower($wgFSPNameStyle) : 'default'; + $this->ForumSettings['EnableBanCheck'] = !empty($wgFSPEnableBanCheck) ? true : false; + $this->ForumSettings['ForumDatabaseProvider'] = !empty($wgFSPDatabaseProvider) ? strtolower($wgFSPDatabaseProvider) : 'mysql'; + + // Bring grous in, if they do not exist, default to a empty array. + foreach (array( + 'LoginDeniedGroups' => 'wgFSPDenyGroups', + 'LoginAllowedGroups' => 'wgFSPAllowGroups', + 'AdminGroups' => 'wgFSPAdminGroups', + 'SuperAdminGroups' => 'wgFSPSuperGroups', + 'InterfaceGroups' => 'wgFSPInterfaceGroups', + 'SpecialGroups' => 'wgFSPSpecialGroups', + ) as $key => $value) + $this->ForumSettings[$key] = !empty($$value) ? $$value : array(); + } + + /** + * Decode the forum software cookies. + * We wil handle some basics here then send off to the forum software provider + * to do the decoding and reeturn a id and password to be validated later. + * We ensure that the ID is a int and password a string. + * + * @return void No return is expected. + */ + private function decodeCookie() + { + // Set the defaults. + $this->ForumCookie['id'] = 0; + $this->ForumCookie['password'] = null; + + $this->MWlogger->debug('Loading the cookie using provider: {software}', array('software' => $this->ForumSoftware)); + + // No cookie? No luck! + if (!$this->fs->cookieExists()) + { + $this->MWlogger->debug('No Cookie present, aborting integration.'); + return; + } + + // This should validate the cookie and return the id/password. + list($this->ForumCookie['id'], $this->ForumCookie['password']) = $this->fs->decodeCookie(); + + $this->ForumCookie['id'] = (int) $this->ForumCookie['id']; + $this->ForumCookie['password'] = (string) $this->ForumCookie['password']; + + $this->MWlogger->debug('Read the cookie, possible member ID "{FSID}" found', array('FSID' => $this->ForumCookie['id'])); + } + + /** + * Sets up a database connection in the forum software. + * If we are using MySQL(i) and have the mysqli class avaiaiable, we use it, otherwise + * we simply use the generic PDO handler. + * We pass on the logger object handler and the current database type to the class. + * Database type should be mysql, mysqli or postgresql. + * + * @return void No return is expected. + */ + private function setupDatabaseProvider() + { + if ( + (!empty($this->ForumSettings['ForumDatabaseProvider']) && $this->ForumSettings['ForumDatabaseProvider'] == 'mysql') + || ($this->ForumSettings['db_type'] === 'mysql' && class_exists('mysqli')) + ) + $databaseClass = 'ForumDatabaseProviderMySQLi'; + else + $databaseClass = 'ForumDatabaseProviderPDO'; + + $this->db = new $databaseClass($this->MWlogger, $this->ForumSettings['db_type']); + + $this->db->connect($this->ForumSettings['db_server'], $this->ForumSettings['db_user'], $this->ForumSettings['db_passwd'], $this->ForumSettings['db_name']); + } + + /** + * Fetch the Forum Member information from the forum software database. + * This will attempt to cache this information for future usage to reduce queries against + * our forum software database. This information is cached at the interval provided. + * + * @return array All the data provided by the forum software for this specific member. + */ + private function getForumMember(WebRequest $request) + { + // Simple caching? + try + { + if (method_exists(\MediaWiki\MediaWikiServices::getInstance(), 'getLocalServerObjectCache')) + $cache = \MediaWiki\MediaWikiServices::getInstance()->getLocalServerObjectCache(); + } catch (MWException $e) { + } + + // Use another caching method. + if (!is_object($cache)) + $cache = new EmptyBagOStuff(); + + // See if this queue is in Cache, makeKey uses wiki id, but not member id. + if (is_object($cache)) + $key = $cache->makeKey( + 'SessionProviders', + 'ForumSessionProvider_' . ((int) $this->ForumCookie['id']) . filemtime(__FILE__) + ); + + // Attempt to retrieve this from the cache. + $data = $cache->get($key); + if (!empty($data)) + { + $this->MWlogger->debug('Found a cached instance of this data, using it'); + $this->ForumMember = (array) $data; + return (array) $data; + } + + $this->MWlogger->debug('Querying Forum Provider for member data'); + + // Ask the forum software for the information. + $this->ForumMember = $this->fs->getForumMember((int) $this->ForumCookie['id']); + + // Cache this up. + if (is_object($cache)) + $cache->set($key, $this->ForumMember, $this->forum_member_cache_interval); + + return $this->ForumMember; + } + + /** + * Cleans up a username to a specific format and returns the cleaned up name for use later. + * Methods are: + * smf: Cleans name by replacing characters incompatible in MediaWiki with characters invalid in SMF. + * domain: Validates name matches a standard ASCII character set, rejects them if not. + * default: Validates name matches a usable username by rejecting their name if it contains invalid MediaWiki characters. + * + * @param string $userName The original username from the forum. + * @return string|null The cleaned name or if invalid null. + */ + private function cleanupUserName(string $userName) + { + $this->MWlogger->debug('Cleanup name "{FSNAME}" using method {FSMMETHOD}', array( + 'FSNAME' => $userName, + 'FSMMETHOD' => strtolower($this->ForumSettings['NameStyle']), + )); + + $userName = ucfirst($userName); + + // Does the forum provider have method we want to use. + if (method_exists($this->fs, 'cleanupUserName')) + { + $userName = $this->fs->cleanupUserName($userName); + + // If we told false, we know to fail through, otherwise we will continue on below. + if ($userName === false) + return null; + } + + switch (strtolower($this->ForumSettings['NameStyle'])) + { + case 'smf': + // Generally backwards compatible with former SMF/Elkarte Auth plugins. + $userName = str_replace('_', '\'', $userName); + $userName = strtr($userName, array('[' => '=', ']' => '"', '|' => '&', '#' => '\\', '{' => '==', '}' => '""', '@' => '&&', ':' => '\\\\')); + break; + case 'domain': + // A more restrictive policy. + if ($userName !== preg_replace('`[^a-zA-Z0-9 .-]+`i', '', $userName)) + return null; + break; + default: + // Just kick them if they have an unusable username. + if (preg_match('`[#<>[\]|{}@:]+`', $userName)) + return null; + } + + $this->MWlogger->debug('Cleanuped name "{FSNAME}"', array('FSNAME' => $userName)); + + return $userName; + } + + /** + * Check if the name or email needs updated. If so, we instruct MediaWiki to save the changes + * to MediaWiki. If we have any legacy checks to make, we ask the forum software provider to + * make those. + * + * @return void No return is expected. + */ + private function updateWikiUser() + { + $this->MWlogger->debug('Updating wiki user.'); + + $userChanged = false; + if ($this->wikiMember->getEmail() !== $this->fs->getMemberEmailAddress($this->ForumMember)) + { + $this->MWlogger->debug('Email Sync Reequired. "{OLD}" vs "{NEW}"', array( + 'OLD' => $this->wikiMember->getEmail(), + 'NEW' => $this->fs->getMemberEmailAddress($this->ForumMember), + )); + + $this->wikiMember->setEmail($this->fs->getMemberEmailAddress($this->ForumMember)); + $this->wikiMember->mEmailAuthenticated = wfTimestampNow(); + $userChanged = true; + } + + if ($this->wikiMember->getRealName() !== $this->fs->getMemberRealName($this->ForumMember)) + { + $this->MWlogger->debug('Real Name Sync Reequired. "{OLD}" vs "{NEW}"', array( + 'OLD' => $this->wikiMember->getRealName(), + 'NEW' => $this->fs->getMemberRealName($this->ForumMember), + )); + + $this->wikiMember->setRealName($this->fs->getMemberRealName($this->ForumMember)); + $userChanged = true; + } + + // Do we have a legacy update to make? + if (method_exists($this->fs, 'legacyUpdateWikiUser')) + $userChanged |= $this->fs->legacyUpdateWikiUser($this->ForumMember, $this->wikiMember); + + // No need to save if nothing has happened + if ($userChanged) + { + $this->MWlogger->debug('Saved wiki user changes.'); + + $this->wikiMember->setOption('forum_last_update_user', time()); + $this->wikiMember->saveSettings(); + } + else + $this->MWlogger->debug('No changes to sync.'); + } + + /** + * Check if our member needs added or removed from specific groups to update + * the members access to MediaWiki. This uses the standard group settings but also + * allows for extending to customized groups or non standard groups. + * + * @return void No return is expected. + */ + private function updateWikiUserGroups() + { + $this->MWlogger->debug('Updating wiki groups...'); + + // Wiki Group Name => Forum Group IDS + $groupActions = array( + 'sysop' => $this->ForumSettings['AdminGroups'], + 'interface-admin' => $this->ForumSettings['InterfaceGroups'], + 'bureaucrat' => $this->ForumSettings['SuperAdminGroups'], + ); + + // Add in our special groups. + foreach ($this->ForumSettings['SpecialGroups'] as $fs_group_id => $wiki_group_name) + { + // Group didn't exist? + if (!isset($groupActions[$wiki_group_name])) + $groupActions[$wiki_group_name] = array(); + + // Add the Forum group into the wiki group. + $groupActions[$wiki_group_name][] = $fs_group_id; + } + + // Now we are going to check all the groups, ignoring updating if nothing has changed. + $madeChange = false; + foreach ($groupActions as $wiki_group_name => $fs_group_ids) + { + // They are in the Forum group but not the wiki group? + if ( + array_intersect($fs_group_ids, $this->ForumMemberGroups) != array() + && in_array($wiki_group_name, $this->wikiMember->getEffectiveGroups()) == array() + ) + { + $this->wikiMember->addGroup($wiki_group_name); + $madeChange = true; + } + // They are not in the Forum group, but in the wiki group + elseif ( + array_intersect($fs_group_ids, $this->ForumMemberGroups) == array() + && in_array($wiki_group_name, $this->wikiMember->getEffectiveGroups()) != array() + ) + { + $this->wikiMember->removeGroup($wiki_group_name); + $madeChange = true; + } + } + + // No need to save if nothing has happened + if ($madeChange) + { + $this->MWlogger->debug('Saved wiki group changes...'); + + $this->wikiMember->setOption('forum_last_update_groups', time()); + $this->wikiMember->saveSettings(); + } + } + + /** + * Create our user in MediaWiki. + * This also sets a security check to attempt to prevent account takeovers by later on + * checking the ids match prior to authorizing the user. + * + * @return void No return is expected. + */ + private function createWikiUser() + { + $this->MWlogger->debug('User does not exist in wiki, creating user...'); + + $this->wikiMember->setName($this->ForumMemberNameCleaned); + $this->wikiMember->setEmail($this->fs->getMemberEmailAddress($this->ForumMember)); + $this->wikiMember->setRealName($this->fs->getMemberRealName($this->ForumMember)); + $this->wikiMember->mEmailAuthenticated = wfTimestampNow(); + + $this->wikiMember->addToDatabase(); + + // This is so we can validate which wiki members are attributed to which forum members. + // Could be used used in the future to prevent account takeovers due to account renames. + $this->wikiMember->setOption('forum_member_id', $this->fs->getMemberID($this->ForumMember)); + + $this->wikiMember->setOption('forum_last_update', time()); + $this->wikiMember->saveSettings(); + } + + /** + * Checks if a member is banned on the forum software if enabled. + * As this may be extensive or resource intensive, this check is cached. + * This is handed off to the forum software provider. + * + * @return bool True if they are banned, false if they are not. + */ + private function memberIsBannedOnForum() + { + // Disbled ban check? + if (empty($this->ForumSettings['EnableBanCheck'])) + return false; + + $this->MWlogger->debug('Checking ban status.'); + + // Check their ban once every 5 minutes. + if (!(time() > ((int) $this->getUserOption('forum_last_update_banx') + $this->banned_check_interval))) + { + $this->MWlogger->debug('Cached banned status is {BAN}', array('BAN' => $this->getUserOption('forum_last_update_ban') !== 0 ? 'NOT banned' : 'banned')); + return $this->getUserOption('forum_is_banned', 'bool'); + } + + // Ask the forum if this member is banned. + $banned = $this->fs->checkBans($this->ForumMember); + + $this->MWlogger->debug('Ban check completed, User is {BAN}', array('BAN' => $banned !== 0 ? 'NOT banned' : 'banned')); + + // Cache this for future hits. + $this->setUserOption('forum_last_update_ban', time()); + $this->setUserOption('forum_is_banned', $banned, 'boo'); + + return $banned; + } + + /** + * Wraps up using the MediaWiki Options to get user options. + * It is deprecated to use the methods under the MediaWiki User Instance. + * This will create a object handler if needed. This attempts to use proper methods based off the input type. + * + * @param string $option_name The name of the option we are attempting to load. + * @param string $type The type the option is. Either string|s, bool|b or int|i (default). + * @return mixed If we don't have a member, we return null, otherwise we pass the return to the MediaWiki handler. + */ + private function getUserOption(string $option_name, string $type = 'int', $default = 0) + { + if (empty($this->wikiMember) || !is_object($this->wikiMember)) + { + $this->MWlogger->debug('Attempted to call getUserOption prior to User Instance existing'); + + return null; + } + + if (empty($this->wikiMemberOptions) || !is_object($this->wikiMemberOptions)) + $this->wikiMemberOptions = \MediaWiki\MediaWikiServices::getInstance()->getUserOptionsManager(); + + if ($type === 'string' || $type === 's') + return $this->wikiMemberOptions->getOption($this->wikiMember, $option_name, $default); + if ($type === 'bool' || $type === 'b') + return $this->wikiMemberOptions->getBoolOption($this->wikiMember, $option_name, $default); + else + return $this->wikiMemberOptions->getIntOption($this->wikiMember, $option_name, $default); + } + + /** + * Wraps up using the MediaWiki Options to set user options. + * It is deprecated to use the methods under the MediaWiki User Instance. + * This will create a object handler if needed. This attempts to use proper methods based off the input type. + * + * @param string $option_name The name of the option we are attempting to load. + * @param mixed $value The value we are setting + * @param string $type The type the option is. Either string|s, bool|b or int|i (default). + * @return mixed If we don't have a member, we return null, otherwise we pass the return to the MediaWiki handler. + */ + private function setUserOption(string $option_name, $value, string $type = 'int') + { + if (empty($this->wikiMember) || !is_object($this->wikiMember)) + { + $this->MWlogger->debug('Attempted to call setUserOption prior to User Instance existing'); + + return null; + } + + if (empty($this->wikiMemberOptions) || !is_object($this->wikiMemberOptions)) + $this->wikiMemberOptions = \MediaWiki\MediaWikiServices::getInstance()->getUserOptionsManager(); + + if ($type === 'string' || $type === 's') + return $this->wikiMemberOptions->setOption($this->wikiMember, $option_name, (string) $value); + elseif ($type === 'bool' || $type === 'b') + return $this->wikiMemberOptions->setOption($this->wikiMember, $option_name, (bool) $value); + else + return $this->wikiMemberOptions->setOption($this->wikiMember, $option_name, (int) $value); + } +} \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..99c95b6 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,17 @@ +ForumSessionProvider License +============================ +Copyright (c) 2020, Simple Machines +Copyright (c) 2019, SleePy +Copyright (c) 2019, Vekseid + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/License.txt b/License.txt deleted file mode 100644 index 6930a79..0000000 --- a/License.txt +++ /dev/null @@ -1,10 +0,0 @@ -Copyright © 2019 Simple Machines. All rights reserved. - - Developed by: Simple Machines Forum Project - Simple Machines - https://www.simplemachines.org - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - [x] Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers. - [x] Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimers in the documentation and/or other materials provided with the distribution. - [x] Neither the names of Simple Machines Forum, Simple Machines, nor the names of its contributors may be used to endorse or promote products derived from this Software without specific prior written permission. diff --git a/README.md b/README.md new file mode 100644 index 0000000..03ab680 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +This MediaWiki (1.35+) extension allows users in an [Elkarte Forum](https://www.elkarte.net/) or [SMF forum](https://www.simplemachines.org/) to be automatically signed in if they are of the appropriate usergroup while logged into the forum. + + ---- +To use, the contents of the ForumSsoProvider directory need to be placed into extensions/ForumSsoProvider. It is then loaded using the 'new' plugin loading method in LocalSettings.php: + + wfLoadExtension('ForumSsoProvider'); + +# Required LocalSettings +### Path to Forum Software + + $wgSMFPath = '/path/to/smf/root/'; + +### Forum software. Supports smf2.0, smf2.1, elk1.0, elk1.1 + + $wgFSPSoftware = 'smf2.1'; + +# Optional LocalSettings +### Login Groups - Users in this group are signed into MediaWiki. Group 2 in SMF is a fake group relating to all users. + + $wgFSPAllowGroups = array(5); + +### Deny Groups - Prevent users in these groups from being signed into MediaWiki, this is a deny group and takes over the login group. In SMF group 4 is the Newbie group. + + $wgFSPDenyGroups = array(4); + +### Admin Groups - Users in these groups are granted sysop access in MediaWiki. + + $wgFSPAdminGroups = array(1, 3); + +### Super Groups - Users in these groups are granted bureaucrat access in MediaWiki. + + $wgFSPSuperGroups = array(1); + +### Interface Groups - Users in these groups are granted interface-admin access in MediaWiki. + + $wgFSPInterfaceGroups = array(1); + +### Special Groups - An key-valued array of smf_group_id => mediawiki_group_name + + $wgFSPSpecialGroups = array( + 3 => 'special', + ); + +### Ban checks - Enable checking against bans in SMF. If found it prevents access to MediaWiki. + + $wgFSPEnableBanCheck = true; + +### Lockdown permissions to prevent new account creations/modifications. + + $wgGroupPermissions['*']['createaccount'] = false; + $wgGroupPermissions['*']['read'] = true; + $wgGroupPermissions['*']['edit'] = false; + $wgGroupPermissions['*']['createtalk'] = false; + $wgGroupPermissions['*']['createpage'] = false; + $wgGroupPermissions['*']['writeapi'] = false; + $wgGroupPermissions['user']['move'] = true; + $wgGroupPermissions['user']['read'] = true; + $wgGroupPermissions['user']['edit'] = true; + $wgGroupPermissions['user']['upload'] = true; + $wgGroupPermissions['user']['autoconfirmed'] = true; + $wgGroupPermissions['user']['emailconfirmed'] = true; + $wgGroupPermissions['user']['createtalk'] = true; + $wgGroupPermissions['user']['createpage'] = true; + $wgGroupPermissions['user']['writeapi'] = true; + +# Legacy Settings +These settings are used by the legacy Auth_SMF.php. +### Uses the legacy Auth_SMF.php LocalSettings + + define('SMF_IN_WIKI', true); + $wgSMFLogin = true; + +### Login Groups - Users in this group are signed into MediaWiki. Group 2 in SMF is a fake group relating to all users. + + $wgSMFGroupID = array(2); + +### Deny Groups - Prevent users in these groups from being signed into MediaWiki, this is a deny group and takes over the login group. In SMF group 4 is the Newbie group. + + $wgSMFDenyGroupID = array(4); + +### Admin Groups - Users in these groups are granted sysop access in MediaWiki. + + $wgSMFAdminGroupID = array(1, 3); + +### Special Groups - An key-valued array of smf_group_id => mediawiki_group_name + + $wgSMFSpecialGroups = array( + 3 => 'special', + ); + +### Forum Software Cookie. + + $wgCookieDomain = 'domain.tld'; + +Troubleshooting +--------------- + +Set $wgDebugLogFile in your LocalSettings.php: + + $wgDebugLogFile = "/some/private/path/mediawiki.log"; + +Search for ForumSessionProvider and it will tell you what it is thinking. + +This bloats pretty quickly, so you'll want to comment it out after you have resolved your problem. + +---- +Getting New SMF Forks In +------------------------ +If you are familiar with how your fork's authentication works, feel free to submit a pull request. + +Issues or changes +------------------------ +If an issue has occurred, please open a new issue. If you have a change, please submit a pull request. + + + + + + + + + + + + + diff --git a/README.txt b/README.txt deleted file mode 100644 index 593147c..0000000 --- a/README.txt +++ /dev/null @@ -1,179 +0,0 @@ -SMF and MediaWiki Integration -============================= -Author: SleePy (sleepy at simplemachines dot org) -Original Author: Ryan Wagoner (rswagoner at gmail dot com) -Version: 1.15 -Last Modification Date: 2019.12.29 -Last Modified By: SleePy (sleepy at simplemachines dot org) - -How does it work? -================= - -Auth_SMF.php manages the Authentication between MediaWiki and an -already existing SMF forum. By means of cookies and their management -by Auth_SMF.php, SMF and MediaWiki can "talk" one to each other, and -therefore seamlessly bridge both environments. - -Users are created in MediaWiki the first time they access the Wiki -when logged into the forum also. In other words, when a registered -user from your forum is logged in, and access the wiki for the first -time, he/she will be automatically and immediately registered and logged -into the wiki with the pre-assigned rights. - -This extension provides just the framework for bridging MediaWiki and -SMF: each administrator is expected to perform the integration (themes, -linking, etc.) - -Pre-requisites -============== - -Before you start, it is recommended that you: - -- Read this file completely and understand all the steps required - -- Use the same database for MediaWiki and SMF. Use a prefix such as -wiki_ to identify MediaWiki's tables. - -- Have a fully working, independent MediaWiki installation in the server. - -- Perform a full backup of files and database(s). A restore should not be -required, but it does not hurt to be on the safe side. - - -How to install -============== - -Follow this instructions to bridge MediaWiki and SMF. - -1- Download Auth_SMF.php to your computer and then upload this file in -the 'extensions' folder of your MediaWiki installation. - -2.- Download the file LocalSettings.php that is located in the root -folder of your MediaWiki installation. - -3.- Edit LocalSettings.php (but make sure not use Notepad, TextEdit or -other text editor that adds byte order marks to files, or you will -break your wiki). See https://en.wikipedia.org/wiki/Byte_order_mark -for more information about byte order marks. - -4.- If you have setup your wiki to require users to be registered -before being allowed to edit or create pages, the following entry -should be already created in LocalSettings.php: - -# This requires a user be logged into the wiki to make changes. -$wgGroupPermissions['*']['edit'] = false; // MediaWiki Setting - -5.- Scroll down to the end of the file, then copy and paste the -following content: - -# If you experience the issue where you appear to be logged in -# eventhough you are logged out then disable the page cache. -#$wgEnableParserCache = false; -#$wgCachePages = false; - -# SMF Authentication -# To get started you only need to configure wgSMFPath. -# The rest of the settings are optional for advanced features. - -# Relative path to the forum directory from the wiki -# Do not put a trailing / -# Example: /public_html/forum and /public_html/wiki -> ../forum -$wgSMFPath = "../forum"; - -# Use SMF's login system to automatically log you in/out of the wiki -# This works best if you are using SMF database sessions (default). -# Make sure "Use database driven sessions" is checked in the -# SMF Admin -> Server Settings -> Feature Configuration section -# NOTE: Make sure to configure the $wgCookieDomain below -#$wgSMFLogin = true; -#$wgCookieDomain = 'domain.com'; - -# Members in these SMF groups will not be allowed to sign into wiki. -# This is useful for denying access to wiki and a easy anti-spam -# method. The group ID, which can be found in the url (;group=XXX) -# when viewing the group from the administrator control panel. -#$wgSMFDenyGroupID = array(4); - -# Grant members of this SMF group(s) access to the wiki -# NOTE: The wgSMFDenyGroupID group supersedes this. -#wgSMFGroupID = array(2); - -# Grant members of this SMF group(s) wiki sysop privileges -# NOTE: These members must be able to login to the wiki -#$wgSMFAdminGroupID = array(1, 3); - -# SMF to wiki group translation. This allows us to assign wiki groups -# to those in certain SMF groups. -#$wgSMFSpecialGroups = array( -# // SMF Group ID => Wiki group name, -# 5 => 'autoconfirmed' -#); - -# THIS MUST BE ADDED. This prevents direct access to the Auth file. -define('SMF_IN_WIKI', true); - -# Load up the extension -require_once "$IP/extensions/Auth_SMF.php"; -$wgAuth = new Auth_SMF(); - -6.- Upload the LocalSettings.php and overwrite the existing one -(remember that it is always a good idea to have a backup) - -7.- Test your integration. If everything went fine, you should have -full authentication bridging. If not, continue reading for some -troubleshooting guidance. - -Known Issues / FAQ -================== - -Q.- The authentication does not detect my cookies/I do not get logged in. - -A.- Check your SMF cookie settings in ACP -> Configuration -> Server Settings -> Cookies and Sessions. You will need to disable local storage of cookies. If the wiki is on a different subdomain, you will need to enable subdomain independent cookies. This auth can not work cross domain (i.e. domainA.com to domainB.com) as it violates security controls in browsers. - -Q.- Authentication is working and permissions are being granted, however -when I try to edit pages, I receive a message edition was not successful -due to loss of session data. It recommends to log off and login again, -but that does not help. - -A.- Make sure that $wgCookieDomain is set to the name of your domain -without prefixes (e.g.: if your forum is located at https://www.myforum.com -then it should be configured as $wgCookieDomain = 'myforum.com'; ) - -Q.- I cannot login with the Administrator account created during MediaWiki's -installation - -A.- This is the expected behavior. The account created at installation time -is part of MediaWiki, not of your forum. This extension bridges SMF to -MediaWiki, but not the other way round (in other words, SMF is the principal -and MediaWiki is a student ;-) - -Q.- I can authenticate with my forum's administrator, however I cannot see how -to assign some extra rights for some users (e.g., for forum moderators). - -A.- That's part of the 'bureaucrat' role. You will need to assign your -administrator to that role first. For some possible techniques, see -https://www.mediawiki.org/wiki/Manual:Setting_user_rights_in_MediaWiki - -Q.- How can I link my wiki from my forum ? - -A.- See https://www.simplemachines.org/community/index.php?topic=261880.0 - -Q.- How can I "bridge" the favicon from my forum ? - -A.- Edit LocalSettings.php and use $wgFavicon = "/path/to/your/icon.ico"; - -Notes -================== - -Feel free to fork this repository and make your desired changes. - -Please see the "Developer's Certificate of Origin"(https://github.com/SimpleMachines/smf-mw-auth/blob/master/DCO.txt) in the repository: -by signing off your contributions, you acknowledge that you can and do license your submissions under the license of the project. - -How to contribute -================== -1. fork the repository. If you are not used to Github, please check out "fork a repository"(https://help.github.com/fork-a-repo). -2. branch your repository, to commit the desired changes. -3. sign-off your commits, to acknowledge your submission under the license of the project. - Note: an easy way to do so, is to define an alias for the git commit command, which includes -s switch (reference: "How to create Git aliases"(https://githacks.com/post/1168909216/how-to-create-git-aliases)) -4. send a pull request to us. diff --git a/extension.json b/extension.json new file mode 100644 index 0000000..bd2f291 --- /dev/null +++ b/extension.json @@ -0,0 +1,63 @@ +{ + "name": "ForumSsoProvider", + "version": "2.0.0", + "author": [ + "Simple Machines", + "SleePy", + "Vekseid" + ], + "url": "https://github.com/SimpleMachines/smf-mw-auth", + "description": "Users a Forum Software to provide MediaWiki single-sign on.", + "license-name": "BSD", + "type": "other", + "requires": { + "MediaWiki": ">= 1.35.0" + }, + "config": { + "_prefix": "wgFSP", + "Path": "", + "NameStyle": "smf", + "Software": "elk1.1", + "SuperGroups": [], + "InterfaceGroups": [1], + "AdminGroups": [1], + "AllowGroups": [], + "DenyGroups:": [] + }, + "SessionProviders": { + "ForumSsoProvider": { + "class": "ForumSsoProvider", + "args": [] + } + }, + "AuthManagerAutoConfig": { + "primaryauth": { + "ForumAuthManager": { + "class": "ForumAuthManager", + "args": [] + } + } + }, + "DefaultUserOptions": { + "forum_last_update": 0 + }, + "Hooks": { + "SpecialPageBeforeExecute": [ + "ForumSsoProvider::onSpecialPageBeforeExecute" + ] + }, + "AutoloadClasses": { + "ForumSsoProvider": "ForumSsoProvider.php", + "ForumAuthManager": "ForumAuthManager.php", + "ForumDatabaseProvider": "DatabaseProvider/base.php", + "ForumDatabaseProviderMySQLi": "DatabaseProvider/MySQLi.php", + "ForumDatabaseProviderPDO": "DatabaseProvider/PDO.php", + "ForumSoftwareProvider": "ForumProvider/base.php", + "ForumSoftwareProvidersmf20": "ForumProvider/smf2.0.php", + "ForumSoftwareProvidersmf21": "ForumProvider/smf2.1.php", + "ForumSoftwareProviderelk10": "ForumProvider/elk1.0.php", + "ForumSoftwareProviderelk11": "ForumProvider/elk1.1.php" + + }, + "manifest_version": 1 +} \ No newline at end of file