Jump to content

Custom login handler


Mikkel-T

Recommended Posts

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:

  1. Read (although it seems somewhat outdated, there is still some good info)
  2. Enabled DEV mode
  3. Created the appropiate file structure:
    NuR1fNa.png
    1. Using the following namespace: namespace IPS\Login\Handler;
    2. And class name: class _Reborn extends \IPS\Login\Handler
  4. Extended the handler array in Handler.php with:  'IPS\Login\Handler\Reborn',
  5. I get the following error when trying to add my login handler:
    GcinR8x.png

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

 

Link to comment
Share on other sites

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:

hj8wLBc.png

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.

Link to comment
Share on other sites

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

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...