Invision Community 4: SEO, prepare for v5 and dormant account notifications By Matt Monday at 02:04 PM
Mikkel-T Posted June 29, 2019 Posted June 29, 2019 Hi, I'm working on migrating us from vbulletin to IPB. In order to do this, we need a custom login handler, to handle logins via our API. What I did so far: Read (although it seems somewhat outdated, there is still some good info) Enabled DEV mode Created the appropiate file structure: Using the following namespace: namespace IPS\Login\Handler; And class name: class _Reborn extends \IPS\Login\Handler Extended the handler array in Handler.php with: 'IPS\Login\Handler\Reborn', I get the following error when trying to add my login handler: Quite possibly I'm missing a few steps, but would anyone know? I tried to search for up-to-date documentation but find it hard to find, and the only few links I managed to find, doesn't seem to be valid any more. Content of the Reborn.php file: <?php /** * @brief Standard Internal Database Login Handler * @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a> * @copyright (c) Invision Power Services, Inc. * @license https://www.invisioncommunity.com/legal/standards/ * @package Invision Community * @since 12 May 2017 */ namespace IPS\Login\Handler; /* To prevent PHP errors (extending class does not exist) revealing path */ if (!\defined('\IPS\SUITE_UNIQUE_KEY')) { header((isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0') . ' 403 Forbidden'); exit; } /** * Standard Internal Database Login Handler */ class _Reborn extends \IPS\Login\Handler { use UsernamePasswordHandler; /** * Get title * * @return string */ public static function getTitle() { return 'login_handler_Reborn'; } /** * ACP Settings Form * * @param string $url URL to redirect user to after successful submission * @return array List of settings to save - settings will be stored to core_login_handlers.login_settings DB field * @code * return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... ); * @endcode */ public function acpForm() { return array( 'reborn_api_endpoint' => new \IPS\Helpers\Form\Text( 'reborn_api_endpoint', ( isset( $this->settings['reborn_api_endpoint'] ) ) ? $this->settings['reborn_api_endpoint'] : '', TRUE ), 'reborn_api_key' => new \IPS\Helpers\Form\Text( 'reborn_api_key', ( isset( $this->settings['reborn_api_key'] ) ) ? $this->settings['reborn_api_key'] : '', TRUE ), ); } /** * Authenticate * * @param \IPS\Login $login The login object * @param string $usernameOrEmail The username or email address provided by the user * @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs * @return \IPS\Member * @throws \IPS\Login\Exception */ public function authenticateUsernamePassword(\IPS\Login $login, $usernameOrEmail, $password) { /* Get member(s) */ if (!$this->authType() & \IPS\Login::AUTH_TYPE_USERNAME) { throw new \IPS\Login\Exception(\IPS\Member::loggedIn()->language()-> addToStack('login_err_no_account', FALSE, array('sprintf' => array(\IPS\Member::loggedIn()->language() ->addToStack('Some settings are messed up')))), \IPS\Login\Exception::NO_ACCOUNT ); } $response = $this->authRequest($usernameOrEmail, $password); if ($response->code != '200') { throw new \IPS\Login\Exception('login_err_bad_password', \IPS\Login\Exception::BAD_PASSWORD, NULL); } $forumId = (int) $response->content->forum_id; $accountId = (int) $response->content->id; $forumUsername = $response->content->nickname; try { $member = \IPS\Member::load($forumId); $member->name = $forumUsername; $member->save(); return $member; } catch (\OutOfRangeException $e) { $member = new \IPS\Member; $member->member_group_id = \IPS\Settings::i()->member_group; $member->name = $usernameOrEmail; $member->save(); //send update to master server, we got a forum_id for this guy! $this->updateForumId($accountId, $member->member_id); return $member; } } /** * Authenticate * * @param \IPS\Member $member The member * @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs * @return bool */ public function authenticatePasswordForMember(\IPS\Member $member, $password) { $request = $this->authRequest($member->name, $password); return $request->code == 200 ? true : false; } private function authRequest($username, $password) { $url = $this->rebornEndPoint . '/api/forum/account/auth'; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, "api_key={$this->rebornApiKey}&username={$username}&password={$password}"); // Receive server response ... curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $server_output = curl_exec($ch); curl_close($ch); $response = json_decode($server_output); return $response; } private function updateForumId($accountId, $forumId) { $url = $this->rebornEndPoint . '/api/forum/account/auth'; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, "api_key={$this->rebornApiKey}&accountId={$accountId}&forumId={$forumId}"); // Receive server response ... curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $server_output = curl_exec($ch); curl_close($ch); $response = json_decode($server_output); return $response; } /** * Can this handler process a login for a member? * * @return bool */ public function canProcess(\IPS\Member $member) { return (bool)$member->members_pass_hash; } /** * Can this handler process a password change for a member? * * @return bool */ public function canChangePassword(\IPS\Member $member) { return false; } /** * Change Password * * @param \IPS\Member $member The member * @param object $newPassword New Password wrapped in an object that can be cast to a string so it doesn't show in any logs * @return void */ public function changePassword(\IPS\Member $member, $newPassword) { //$member->setLocalPassword( $newPassword ); //$member->save(); } /** * Show in Account Settings? * * @param \IPS\Member|NULL $member The member, or NULL for if it should show generally * @return bool */ public function showInUcp(\IPS\Member $member = NULL) { return FALSE; } }
teraßyte Posted June 29, 2019 Posted June 29, 2019 It would probably help more if you can post the code in your Reborn.php file. Just remove any sensitive data before posting it.
Mikkel-T Posted June 29, 2019 Author Posted June 29, 2019 True, added the file. It's just a POC, but couldn't get far enough to test it :C
Mark Posted June 30, 2019 Posted June 30, 2019 The more up-to-date version of the developer documentation is here:
Mikkel-T Posted June 30, 2019 Author Posted June 30, 2019 I tried creating a sample application, and added a hook to it: //<?php /* To prevent PHP errors (extending class does not exist) revealing path */ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) ) { exit; } abstract class reborn_hook_RebornLoginHandler extends _HOOK_CLASS_ { public static function handlerClasses() { $return = parent::handlerClasses(); $return[] = 'IPS\mycustomloginhandler\MyLoginHandler'; return $return; } } Then I created "MyCustomLoginHandler.php" in the root of my newly created application directory: <?php /** * @brief Standard Internal Database Login Handler * @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a> * @copyright (c) Invision Power Services, Inc. * @license https://www.invisioncommunity.com/legal/standards/ * @package Invision Community * @since 12 May 2017 */ namespace IPS\mycustomloginhandler; /* To prevent PHP errors (extending class does not exist) revealing path */ use IPS\Login\Handler\UsernamePasswordHandler; if (!\defined('\IPS\SUITE_UNIQUE_KEY')) { header((isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0') . ' 403 Forbidden'); exit; } /** * Standard Internal Database Login Handler */ class _MyCustomLoginHandler extends \IPS\Login\Handler { use UsernamePasswordHandler; /** * Get title * * @return string */ public static function getTitle() { return 'login_handler_Reborn'; } /** * @brief Can we have multiple instances of this handler? */ public static $allowMultiple = false; /** * ACP Settings Form * * @param string $url URL to redirect user to after successful submission * @return array List of settings to save - settings will be stored to core_login_handlers.login_settings DB field * @code * return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... ); * @endcode */ public function acpForm() { return array( 'reborn_api_endpoint' => new \IPS\Helpers\Form\Text( 'reborn_api_endpoint', ( isset( $this->settings['reborn_api_endpoint'] ) ) ? $this->settings['reborn_api_endpoint'] : '', TRUE ), 'reborn_api_key' => new \IPS\Helpers\Form\Text( 'reborn_api_key', ( isset( $this->settings['reborn_api_key'] ) ) ? $this->settings['reborn_api_key'] : '', TRUE ), ); } /** * Authenticate * * @param \IPS\Login $login The login object * @param string $usernameOrEmail The username or email address provided by the user * @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs * @return \IPS\Member * @throws \IPS\Login\Exception */ public function authenticateUsernamePassword(\IPS\Login $login, $usernameOrEmail, $password) { /* Get member(s) */ if (!$this->authType() & \IPS\Login::AUTH_TYPE_USERNAME) { throw new \IPS\Login\Exception(\IPS\Member::loggedIn()->language()-> addToStack('login_err_no_account', FALSE, array('sprintf' => array(\IPS\Member::loggedIn()->language() ->addToStack('Some settings are messed up')))), \IPS\Login\Exception::NO_ACCOUNT ); } $response = $this->authRequest($usernameOrEmail, $password); if ($response->code != '200') { throw new \IPS\Login\Exception('login_err_bad_password', \IPS\Login\Exception::BAD_PASSWORD, NULL); } $forumId = (int) $response->content->forum_id; $accountId = (int) $response->content->id; $forumUsername = $response->content->nickname; try { $member = \IPS\Member::load($forumId); $member->name = $forumUsername; $member->save(); return $member; } catch (\OutOfRangeException $e) { $member = new \IPS\Member; $member->member_group_id = \IPS\Settings::i()->member_group; $member->name = $usernameOrEmail; $member->save(); //send update to master server, we got a forum_id for this guy! $this->updateForumId($accountId, $member->member_id); return $member; } } /** * Authenticate * * @param \IPS\Member $member The member * @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs * @return bool */ public function authenticatePasswordForMember(\IPS\Member $member, $password) { $request = $this->authRequest($member->name, $password); return $request->code == 200 ? true : false; } private function authRequest($username, $password) { $url = $this->rebornEndPoint . '/api/forum/account/auth'; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, "api_key={$this->rebornApiKey}&username={$username}&password={$password}"); // Receive server response ... curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $server_output = curl_exec($ch); curl_close($ch); $response = json_decode($server_output); return $response; } private function updateForumId($accountId, $forumId) { $url = $this->rebornEndPoint . '/api/forum/account/auth'; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, "api_key={$this->rebornApiKey}&accountId={$accountId}&forumId={$forumId}"); // Receive server response ... curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $server_output = curl_exec($ch); curl_close($ch); $response = json_decode($server_output); return $response; } /** * Can this handler process a login for a member? * * @return bool */ public function canProcess(\IPS\Member $member) { return (bool)$member->members_pass_hash; } /** * Can this handler process a password change for a member? * * @return bool */ public function canChangePassword(\IPS\Member $member) { return false; } /** * Change Password * * @param \IPS\Member $member The member * @param object $newPassword New Password wrapped in an object that can be cast to a string so it doesn't show in any logs * @return void */ public function changePassword(\IPS\Member $member, $newPassword) { //$member->setLocalPassword( $newPassword ); //$member->save(); } /** * Show in Account Settings? * * @param \IPS\Member|NULL $member The member, or NULL for if it should show generally * @return bool */ public function showInUcp(\IPS\Member $member = NULL) { return FALSE; } } But upon trying to create a new handler from the adminCP, I still encounter this error: But at least I'm no longer modifying the original files 😛 I also tried changing the hook to return $return[] = 'IPS\mycustomloginhandler\MyCustomLoginHandler'; as this seemed more correct to me, but it gave same results.
newbie LAC Posted June 30, 2019 Posted June 30, 2019 Hello, 15 hours ago, Mikkel-T said: try { $member = \IPS\Member::load($forumId); The load method doesn't throw exception For non existing members will be return Guest Correct $member = \IPS\Member::load($id); if ($member->member_id) { // User } else { // Guest } \IPS\Member::load($forumId); Load by $forumId. Is this correct? $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); you can use built-in methods try { $response = \IPS\Http\Url::external($this->rebornEndPoint . '/api/forum/account/auth')->request()->post(array('api_key' => $this->rebornApiKey, 'accountId' => $accountId, 'forumId' => $forumId))->decodeJson(false); } catch (\Exception $e) { } 27 minutes ago, Mikkel-T said: Then I created "MyCustomLoginHandler.php" in the root of my newly created application directory: Also look at $return[] = 'IPS\mycustomloginhandler\MyLoginHandler'; and class _MyCustomLoginHandler extends \IPS\Login\Handler MyLoginHandler vs. MyCustomLoginHandler
Mark Posted June 30, 2019 Posted June 30, 2019 Change "mycustomloginhandler" to your app's key ("reborn"?)
Recommended Posts
Archived
This topic is now archived and is closed to further replies.