Seditio Source
Root |
./othercms/ips_4.3.4/system/MFA/SecurityQuestions/Handler.php
<?php
/**
 * @brief        Multi Factor Authentication Handler for Security Questions
 * @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        2 Sep 2016
 */

namespace IPS\MFA\SecurityQuestions;

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

/**
 * Multi Factor Authentication Handler for Security Questions
 */
class _Handler extends \IPS\MFA\MFAHandler
{    
   
/**
     * @brief    Key
     */
   
protected $key = 'questions';
   
   
/* !Setup */
   
    /**
     * Handler is enabled
     *
     * @return    bool
     */
   
public function isEnabled()
    {
        return \
IPS\Settings::i()->security_questions_enabled;
    }
       
   
/**
     * Member *can* use this handler (even if they have not yet configured it)
     *
     * @return    bool
     */
   
public function memberCanUseHandler( \IPS\Member $member )
    {
        return \
IPS\Settings::i()->security_questions_groups == '*' or $member->inGroup( explode( ',', \IPS\Settings::i()->security_questions_groups ) );
    }
   
   
/**
     * Member has configured this handler
     *
     * @param    \IPS\Member    $member    The member
     * @return    bool
     */
   
public function memberHasConfiguredHandler( \IPS\Member $member )
    {
        return
$member->members_bitoptions['has_security_answers'];
    }
   
   
/**
     * Show a setup screen
     *
     * @param    \IPS\Member        $member                        The member
     * @param    bool            $showingMultipleHandlers    Set to TRUE if multiple options are being displayed
     * @param    \IPS\Http\Url    $url                        URL for page
     * @return    string
     */
   
public function configurationScreen( \IPS\Member $member, $showingMultipleHandlers=FALSE, \IPS\Http\Url $url )
    {
       
$securityQuestions = array();
        foreach (
Question::roots() as $question )
        {
           
$securityQuestions[ $question->id ] = $question->_title;
        }
               
        return \
IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->securityQuestionsSetup( $securityQuestions, $showingMultipleHandlers );
    }
   
   
/**
     * Submit configuration screen. Return TRUE if was accepted
     *
     * @param    \IPS\Member        $member    The member
     * @return    bool
     */
   
public function configurationScreenSubmit( \IPS\Member $member )
    {
       
$answers = array();
       
       
$isReconfiguring = $this->memberHasConfiguredHandler( $member );
       
        foreach ( \
IPS\Request::i()->security_question as $k => $v )
        {
           
$answers[ $v ] = array(
               
'answer_question_id'    => $v,
               
'answer_member_id'        => $member->member_id,
               
'answer_answer'            => \IPS\Text\Encrypt::fromPlaintext( \IPS\Request::i()->security_answer[ $k ] )->tag()
            );
        }
       
        if (
count( $answers ) >= \IPS\Settings::i()->security_questions_number )
        {        
            \
IPS\Db::i()->delete( 'core_security_answers', array( 'answer_member_id=?', $member->member_id ) );
            \
IPS\Db::i()->insert( 'core_security_answers', $answers );
           
           
$member->members_bitoptions['has_security_answers'] = TRUE;
           
$member->save();

           
/* Log MFA Enable */
           
$member->logHistory( 'core', 'mfa', array( 'handler' => $this->key, 'enable' => TRUE, 'reconfigure' => $isReconfiguring ) );

            return
TRUE;
        }
       
        return
FALSE;
    }
   
   
/* !Authentication */
   
    /**
     * Get the form for a member to authenticate
     *
     * @param    \IPS\Member        $member        The member
     * @param    \IPS\Http\Url    $url        URL for page
     * @return    string
     */
   
public function authenticationScreen( \IPS\Member $member, \IPS\Http\Url $url )
    {
        try
        {
           
$chosenAnswer = \IPS\Db::i()->select( '*', 'core_security_answers', array( 'answer_member_id=? AND answer_is_chosen=1', $member->member_id ) )->first();
           
$chosenQuestion = Question::load( $chosenAnswer['answer_question_id'] );
        }
        catch ( \
Exception $e )
        {
           
$chosenQuestion = NULL;
            foreach ( \
IPS\Db::i()->select( '*', 'core_security_answers', array( 'answer_member_id=?', $member->member_id ), 'RAND()' ) as $chosenAnswer )
            {
                try
                {
                   
$chosenQuestion = Question::load( $chosenAnswer['answer_question_id'] );
                    \
IPS\Db::i()->update( 'core_security_answers', array( 'answer_is_chosen' => 1 ), array( 'answer_member_id=? AND answer_question_id=?', $member->member_id, $chosenQuestion->id ) );
                    break;
                }
                catch ( \
OutOfRangeException $e ) { }
            }
            if ( !
$chosenQuestion )
            {
                return
NULL;
            }
        }
       
        return \
IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->securityQuestionsAuth( $chosenQuestion );
    }
   
   
/**
     * Submit authentication screen. Return TRUE if was accepted
     *
     * @param    \IPS\Member        $member    The member
     * @return    string
     */
   
public function authenticationScreenSubmit( \IPS\Member $member )
    {
        try
        {
           
$authenticated = \IPS\Request::i()->security_answer == \IPS\Text\Encrypt::fromTag( \IPS\Db::i()->select( 'answer_answer', 'core_security_answers', array( 'answer_member_id=? AND answer_is_chosen=1', $member->member_id ) )->first() )->decrypt();
           
            if(
$authenticated )
            {
                \
IPS\Db::i()->update( 'core_security_answers', array( 'answer_is_chosen' => 0 ), array( 'answer_member_id=? AND answer_is_chosen=?', $member->member_id, 1 ) );
            }

            return
$authenticated;
        }
        catch ( \
UnderflowException $e )
        {
            return
FALSE;
        }
    }
   
   
/* !ACP */
   
    /**
     * Toggle
     *
     * @param    bool    $enabled    On/Off
     * @return    bool
     */
   
public function toggle( $enabled )
    {
        \
IPS\Settings::i()->changeValues( array( 'security_questions_enabled' => $enabled ) );
    }
   
   
/**
     * ACP Settings
     *
     * @return    string
     */
   
public function acpSettings()
    {
       
/* Init */
       
$activeTabContents = '';
       
$tabs = array(
           
'settings'     => 'security_question_settings',
           
'questions'    => 'security_questions_questions'
       
);
       
$activeTab = ( isset( \IPS\Request::i()->tab ) and array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'handlers';
       
       
/* Settings Form */
       
if ( $activeTab === 'settings' )
        {
           
$form = new \IPS\Helpers\Form;
           
$form->add( new \IPS\Helpers\Form\Select( 'security_questions_groups', \IPS\Settings::i()->security_questions_groups == '*' ? '*' : explode( ',', \IPS\Settings::i()->security_questions_groups ), FALSE, array(
               
'multiple'        => TRUE,
               
'options'        => array_combine( array_keys( \IPS\Member\Group::groups() ), array_map( function( $_group ) { return (string) $_group; }, \IPS\Member\Group::groups() ) ),
               
'unlimited'        => '*',
               
'unlimitedLang'    => 'everyone'
           
), NULL, NULL, NULL, 'security_questions_groups' ) );
           
$form->add( new \IPS\Helpers\Form\Radio( 'security_questions_prompt', \IPS\Settings::i()->security_questions_prompt, FALSE, array( 'options' => array( 'register' => 'security_questions_prompt_register', 'optional' => 'security_questions_prompt_optional', 'access' => 'security_questions_prompt_access' ) ), NULL, NULL, NULL, 'security_questions_prompt' ) );
           
$form->add( new \IPS\Helpers\Form\Number( 'security_questions_number', \IPS\Settings::i()->security_questions_number, FALSE, array( 'min' => 1, 'max' => \IPS\Db::i()->select( 'COUNT(*)', 'core_security_questions' )->first() ?: NULL ), NULL, NULL, NULL, 'security_questions_number' ) );
           
            if (
$values = $form->values() )
            {
               
$values['security_questions_groups'] = ( $values['security_questions_groups'] == '*' ) ? '*' : implode( ',', $values['security_questions_groups'] );
               
$form->saveAsSettings( $values );            
                \
IPS\Session::i()->log( 'acplogs__mfa_handler_enabled', array( "mfa_questions_title" => TRUE ) );
                \
IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=settings&controller=mfa' ), 'saved' );
            }
           
           
$activeTabContents = (string) $form;
        }
       
       
/* Questions table */
       
else
        {
           
$controller = new \IPS\core\modules\admin\settings\securityquestions;
           
$controller->execute();
           
$activeTabContents = \IPS\Output::i()->output;
        }
       
       
       
/* Output */
       
if( \IPS\Request::i()->isAjax() )
        {
            return
$activeTabContents;
        }
        else
        {
            return \
IPS\Theme::i()->getTemplate( 'global' )->tabs( $tabs, $activeTab, $activeTabContents, \IPS\Http\Url::internal( "app=core&module=settings&controller=mfa&tab=handlers&do=settings&key=questions" ) );
        }
    }
   
   
/**
     * Configuration options when editing member account in ACP
     *
     * @param    \IPS\Member            $member        The member
     * @return    array
     */
   
public function acpConfiguration( \IPS\Member $member )
    {
       
$return = array();
       
$return[] = new \IPS\Helpers\Form\YesNo( "mfa_{$this->key}_title", $this->memberHasConfiguredHandler( $member ), FALSE, array( 'togglesOn' => array( 'security_question_matrix' ) ) );
       
       
$securityQuestions = array();
        foreach ( \
IPS\MFA\SecurityQuestions\Question::roots() as $question )
        {
           
$securityQuestions[ $question->id ] = $question->_title;
        }
       
       
$matrix = new \IPS\Helpers\Form\Matrix('security_question_matrix');
       
$matrix->columns = array(
           
'security_question_q'    => function( $key, $value, $data ) use ( $securityQuestions )
            {
                return new \
IPS\Helpers\Form\Select( $key, $value, FALSE, array( 'options' => $securityQuestions ) );
            },
           
'security_question_a'    => function( $key, $value, $data )
            {
                return new \
IPS\Helpers\Form\Text( $key, $value );
            },
        );
       
        foreach (
$member->securityAnswers() as $questionId => $answer )
        {
           
$matrix->rows[] = array(
               
'security_question_q'    => $questionId,
               
'security_question_a'    => \IPS\Text\Encrypt::fromTag( $answer )->decrypt()
            );
        }
       
       
$return['security_answers'] = $matrix;
       
        return
$return;
       
    }
   
   
/**
     * Save configuration when editing member account in ACP
     *
     * @param    \IPS\Member        $member        The member
     * @param    array            $values        Values from form
     * @return    array
     */
   
public function acpConfigurationSave( \IPS\Member $member, $values )
    {        
        if ( isset(
$values["mfa_{$this->key}_title"] ) and !$values["mfa_{$this->key}_title"] )
        {
            if (
$this->memberHasConfiguredHandler( $member ) )
            {
               
$this->disableHandlerForMember( $member );
            }
            return;
        }
       
        \
IPS\Db::i()->delete( 'core_security_answers', array( 'answer_member_id=?', $member->member_id ) );
       
       
$toInsert = array();
       
        foreach (
$values['security_question_matrix'] as $row )
        {
            if (
$row['security_question_a'] )
            {
               
$toInsert[ $row['security_question_q'] ] = array(
                   
'answer_question_id'    => $row['security_question_q'],
                   
'answer_member_id'        => $member->member_id,
                   
'answer_answer'            => (string) \IPS\Text\Encrypt::fromPlaintext( $row['security_question_a'] )->tag()
                );
            }
        }
       
        if (
count( $toInsert ) )
        {
            \
IPS\Db::i()->insert( 'core_security_answers', $toInsert );
        }
       
        if (
count( $toInsert ) >= \IPS\Settings::i()->security_questions_number )
        {
           
$member->members_bitoptions['has_security_answers'] = TRUE;
        }
        else
        {
           
$member->members_bitoptions['has_security_answers'] = FALSE;
        }
    }
   
   
/* !Misc */
   
    /**
     * If member has configured this handler, disable it
     *
     * @param    \IPS\Member    $member    The member
     * @return    bool
     */
   
public function disableHandlerForMember( \IPS\Member $member )
    {
        \
IPS\Db::i()->delete( 'core_security_answers', array( 'answer_member_id=?', $member->member_id ) );
       
$member->members_bitoptions['has_security_answers'] = FALSE;
       
$member->save();

       
/* Log MFA Disable */
       
$member->logHistory( 'core', 'mfa', array( 'handler' => $this->key, 'enable' => FALSE ) );
    }
}