Seditio Source
Root |
./othercms/ips_4.3.4/system/Login/Handler/OAuth2/OAuth2.php
<?php
/**
 * @brief        Abstract OAuth2 Login Handler
 * @author        <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
 * @copyright    (c) 2001 - 2016 Invision Power Services, Inc.
 * @license        http://www.invisionpower.com/legal/standards/
 * @package        IPS Community Suite
 * @since        31 May 2017
 * @version        SVN_VERSION_NUMBER
 */

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

/**
 * Abstract OAuth2 Login Handler
 */
abstract class _OAuth2 extends \IPS\Login\Handler
{    
   
/* !Login Handler: Basics */
   
    /**
     * @brief    Any additional scopes to authenticate with
     */
   
public $additionalScopes = NULL;
   
   
/**
     * Get type
     *
     * @return    int
     */
   
public function type()
    {
        if (
$this->grantType() === 'password' )
        {
            return \
IPS\Login::TYPE_USERNAME_PASSWORD;
        }
        else
        {
            return \
IPS\Login::TYPE_BUTTON;
        }
    }
   
   
/**
     * ACP Settings Form
     *
     * @return    array    List of settings to save - settings will be stored to core_login_methods.login_settings DB field
     * @code
         return array( 'savekey'    => new \IPS\Helpers\Form\[Type]( ... ), ... );
     * @endcode
     */
   
public function acpForm()
    {                
       
$return = array(
            array(
'login_handler_oauth_settings', \IPS\Member::loggedIn()->language()->addToStack( static::getTitle() . '_info', FALSE, array( 'sprintf' => array( (string) $this->redirectionEndpoint() ) ) ) ),
           
'client_id'        => new \IPS\Helpers\Form\Text( 'oauth_client_id', isset( $this->settings['client_id'] ) ? $this->settings['client_id'] : NULL, TRUE ),
           
'client_secret'    => new \IPS\Helpers\Form\Text( 'oauth_client_client_secret', isset( $this->settings['client_secret'] ) ? $this->settings['client_secret'] : NULL, NULL, array(), NULL, NULL, NULL, 'client_secret' ),
        );
               
       
$return[] = 'account_management_settings';
       
$return['show_in_ucp'] = new \IPS\Helpers\Form\Radio( 'login_handler_show_in_ucp', isset( $this->settings['show_in_ucp'] ) ? $this->settings['show_in_ucp'] : 'always', FALSE, array(
           
'options' => array(
               
'always'        => 'login_handler_show_in_ucp_always',
               
'loggedin'        => 'login_handler_show_in_ucp_loggedin',
            ),
        ) );
       
$return['update_name_changes'] = new \IPS\Helpers\Form\Radio( 'login_update_name_changes', isset( $this->settings['update_name_changes'] ) ? $this->settings['update_name_changes'] : 'disabled', FALSE, array( 'options' => array(
           
'force'        => 'login_update_changes_yes',
           
'optional'    => 'login_update_changes_optional',
           
'disabled'    => 'login_update_changes_no',
        ) ),
NULL, NULL, NULL, 'login_update_name_changes_inc_optional' );
       
$return['update_email_changes'] = new \IPS\Helpers\Form\Radio( 'login_update_email_changes', isset( $this->settings['update_email_changes'] ) ? $this->settings['update_email_changes'] : 'optional', FALSE, array( 'options' => array(
           
'force'        => 'login_update_changes_yes',
           
'optional'    => 'login_update_changes_optional',
           
'disabled'    => 'login_update_changes_no',
        ) ),
NULL, NULL, NULL, 'login_update_email_changes_inc_optional' );
       
        return
array_merge( $return, parent::acpForm() );        
    }
   
   
/**
     * Test Settings
     *
     * @return    bool
     * @throws    \LogicException
     */
   
public function testSettings()
    {
        try
        {
           
/* Authorization Code / Implicit */
           
if ( $this->grantType() === 'authorization_code' )
            {
               
$response = $this->_authenticatedRequest( $this->tokenEndpoint(), array(
                   
'grant_type'    => 'authorization_code',
                   
'code'            => 'xxx',
                   
'redirect_uri'    => (string) $this->redirectionEndpoint(),
                ) )->
decodeJson();
               
                if ( isset(
$response['error'] ) and $response['error'] === 'invalid_client' )
                {
                    throw new \
LogicException( \IPS\Member::loggedIn()->language()->addToStack( 'oauth_setup_error_secret' ) );
                }
            }
           
/* Password */
           
elseif ( $this->grantType() === 'password' )
            {
               
$response =  $this->_authenticatedRequest( $this->tokenEndpoint(), array(
                   
'grant_type'    => 'password',
                   
'username'        => 'username',
                   
'password'        => 'password',
                ) )->
decodeJson();

                if ( !isset(
$response['error'] ) or $response['error'] !== 'invalid_grant' )
                {
                    throw new \
LogicException( \IPS\Member::loggedIn()->language()->addToStack( 'oauth_setup_error_generic', FALSE, array( 'sprintf' => array( isset( $response['error_description'] ) ? $response['error_description'] : NULL ) ) ) );
                }
            }
        }
        catch( \
IPS\Http\Request\Exception $e )
        {
            throw new \
LogicException( \IPS\Member::loggedIn()->language()->addToStack( 'oauth_setup_error_generic', FALSE, array( 'sprintf' => array( $e->getMessage() ) ) ) );
        }
    }
   
   
/* !Button Authentication */
   
   
use ButtonHandler;
       
   
/**
     * Authenticate
     *
     * @param    \IPS\Login    $login        The login object
     * @return    \IPS\Member
     * @throws    \IPS\Login\Exception
     */
   
public function authenticateButton( \IPS\Login $login )
    {
       
/* If we have a code, process it */
       
if ( $this->grantType() === 'authorization_code' and ( isset( \IPS\Request::i()->code ) or isset( \IPS\Request::i()->error ) ) )
        {
            return
$this->_handleAuthorizationResponse( $login );
        }
       
       
/* If we have a token, process that */
       
elseif ( $this->grantType() === 'implicit' and ( isset( \IPS\Request::i()->access_token ) or isset( \IPS\Request::i()->error ) ) )
        {
            return
$this->_handleAuthorizationResponse( $login );
        }
               
       
/* Otherwise send them to the Authorization Endpoint */
       
else
        {
           
$target = $this->authorizationEndpoint( $login )->setQueryString( array(
               
'client_id'        => $this->settings['client_id'],
               
'response_type'    => $this->grantType() === 'authorization_code' ? 'code' : 'implicit',
               
'redirect_uri'    => (string) $this->redirectionEndpoint(),
               
'state'            => $this->id . '-' . base64_encode( $login->url ) . '-' . \IPS\Session::i()->csrfKey . '-' . \IPS\Request::i()->ref,
            ) );
           
            if (
$scopes = $this->scopesToRequest( isset( \IPS\Request::i()->scopes ) ? explode( ',', \IPS\Request::i()->scopes ) : NULL ) )
            {
               
$target = $target->setQueryString( 'scope', implode( ' ', $scopes ) );
            }
           
            \
IPS\Output::i()->redirect( $target );
        }
    }
   
   
/* !Username/Password Authentication */
   
   
use UsernamePasswordHandler;
   
   
/**
     * Authenticate
     *
     * @param    \IPS\Login    $login                The login object
     * @param    string        $usernameOrEmail    The username or email address provided by the user
     * @param    string        $password            The plaintext password provided by the user
     * @return    \IPS\Member
     * @throws    \IPS\Login\Exception
     */
   
public function authenticateUsernamePassword( \IPS\Login $login, $usernameOrEmail, $password )
    {
       
$data =  array(
           
'grant_type'    => 'password',
           
'username'        => $usernameOrEmail,
           
'password'        => $password,
        );
        if (
$scopes = $this->scopesToRequest() )
        {
           
$data['scope'] = implode( ' ', $scopes );
        }
       
        try
        {
           
$accessToken =  $this->_authenticatedRequest( $this->tokenEndpoint(), $data )->decodeJson();
        }
        catch ( \
Exception $e )
        {
            \
IPS\Log::log( $e, 'oauth' );
            throw new \
IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
        }
       
        if ( isset(
$accessToken['access_token'] ) )
        {
            return
$this->_processAccessToken( $login, $accessToken );
        }
        else
        {
            if ( isset(
$accessToken['error'] ) and $accessToken['error'] === 'invalid_grant' )
            {
                throw new \
IPS\Login\Exception( 'login_bad_username_or_password', \IPS\Login\Exception::NO_ACCOUNT );
            }
           
            \
IPS\Log::log( print_r( $accessToken, TRUE ), 'oauth' );
            throw new \
IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
        }
    }
   
   
/**
     * Authenticate
     *
     * @param    \IPS\Member    $member                The member
     * @param    string        $password            The plaintext password provided by the user
     * @return    bool
     */
   
public function authenticatePasswordForMember( \IPS\Member $member, $password )
    {
        if (
$this->authType() & \IPS\Login::AUTH_TYPE_USERNAME )
        {
            try
            {
               
$response = $this->_authenticatedRequest( $this->tokenEndpoint(), array(
                   
'grant_type'    => 'password',
                   
'username'        => $member->name,
                   
'password'        => $password,
                ) )->
decodeJson();
                if ( isset(
$response['access_token'] ) )
                {
                    return
TRUE;
                }
            }
            catch ( \
Exception $e ) { }
        }
       
        if (
$this->authType() & \IPS\Login::AUTH_TYPE_EMAIL )
        {
            try
            {
               
$response =  $this->_authenticatedRequest( $this->tokenEndpoint(), array(
                   
'grant_type'    => 'password',
                   
'username'        => $member->email,
                   
'password'        => $password,
                ) )->
decodeJson();
                if ( isset(
$response['access_token'] ) )
                {
                    return
TRUE;
                }
            }
            catch ( \
Exception $e ) { }
        }
       
        return
FALSE;
    }
   
   
/* !OAuth Authentication */
   
   
const AUTHENTICATE_HEADER = 'header';
    const
AUTHENTICATE_POST  = 'post';
   
   
/**
     * Should client credentials be sent as an "Authoriation" header, or as POST data?
     *
     * @return    string
     */
   
protected function _authenticationType()
    {
        return static::
AUTHENTICATE_HEADER;
    }
   
   
/**
     * Send request authenticated with client credentials
     *
     * @param    \IPS\Http\Url    $url    The URL
     * @return    \IPS\Http\Response
     */
   
protected function _authenticatedRequest( \IPS\Http\Url $url, $data )
    {
       
$request = $url->request();
       
        if (
$this->_authenticationType() === static::AUTHENTICATE_HEADER )
        {
           
$request = $request->login( $this->settings['client_id'], $this->settings['client_secret'] );
        }
        else
        {
           
$data['client_id'] = $this->settings['client_id'];
           
$data['client_secret'] = $this->settings['client_secret'];
        }
       
        return
$request->post( $data );
    }
   
   
/**
     * Handle authorization response
     *
     * @param    \IPS\Login    $login        The login object
     * @return    \IPS\Member
     * @throws    \IPS\Login\Exception
     */
   
protected function _handleAuthorizationResponse( \IPS\Login $login )
    {
       
/* Did we get an error? */
       
if ( isset( \IPS\Request::i()->error ) )
        {
            if ( \
IPS\Request::i()->error === 'access_denied' )
            {
                return
NULL;
            }
            else
            {
                \
IPS\Log::log( print_r( $_GET, TRUE ), 'oauth' );
                throw new \
IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
            }
        }
       
       
/* If we have a code, swap it for an access token, otherwise, decode what we have */
       
if ( isset( \IPS\Request::i()->code ) )
        {
           
$accessToken = $this->_exchangeAuthorizationCodeForAccessToken( \IPS\Request::i()->code );
        }
        else
        {
           
$accessToken = array(
               
'access_token'    => \IPS\Request::i()->access_token,
               
'token_type'    => isset( \IPS\Request::i()->token_type ) ? \IPS\Request::i()->token_type : 'bearer'
           
);
            if ( isset( \
IPS\Request::i()->expires_in ) )
            {
               
$accessToken['expires_in'] = \IPS\Request::i()->expires_in;
            }
        }
       
       
/* Process */
       
return $this->_processAccessToken( $login, $accessToken );
    }
   
   
/**
     * Process an Access Token
     *
     * @param    \IPS\Login    $login            The login object
     * @param    array        $accessToken    Access Token
     * @return    \IPS\Member
     * @throws    \IPS\Login\Exception
     */
   
protected function _processAccessToken( \IPS\Login $login, $accessToken )
    {        
       
/* Get user id */
       
try
        {
           
$userId = $this->authenticatedUserId( $accessToken['access_token'] );
        }
        catch ( \
Exception $e )
        {
            \
IPS\Log::log( $e, 'oauth' );
            throw new \
IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
        }
       
       
/* What scopes did we get? */
       
if ( isset( $accessToken['scope'] ) )
        {
           
$scope = explode( ' ', $accessToken['scope'] );
        }
        else
        {
           
$scope = $this->scopesIssued( $accessToken['access_token'] );
        }
               
       
/* Has this user signed in with this service before? */
       
try
        {
           
$oauthAccess = \IPS\Db::i()->select( '*', 'core_login_links', array( 'token_login_method=? AND token_identifier=?', $this->id, $userId ) )->first();
           
$member = \IPS\Member::load( $oauthAccess['token_member'] );
           
           
/* If the user never finished the linking process, or the account has been deleted, discard this access token */
           
if ( !$oauthAccess['token_linked'] or !$member->member_id )
            {
                \
IPS\Db::i()->delete( 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $oauthAccess['token_member'] ) );
                throw new \
UnderflowException;
            }
           
           
/* Otherwise, update our token... */
           
\IPS\Db::i()->update( 'core_login_links', array(
               
'token_access_token'    => $accessToken['access_token'],
               
'token_expires'            => isset( $accessToken['expires_in'] ) ? ( time() + intval( $accessToken['expires_in'] ) ) : NULL,
               
'token_refresh_token'    => isset( $accessToken['refresh_token'] ) ? $accessToken['refresh_token'] : NULL,
               
'token_scope'            => $scope ? json_encode( $scope ) : NULL,
            ), array(
'token_login_method=? AND token_member=?', $this->id, $oauthAccess['token_member'] ) );
           
           
/* ... and return the member object */
           
return $member;
        }
       
/* No, create or link the account */
       
catch ( \UnderflowException $e )
        {
           
/* Get the username + email */
           
$name = NULL;
            try
            {
               
$name = $this->authenticatedUserName( $accessToken['access_token'] );
            }
            catch ( \
Exception $e ) {}
           
           
$email = NULL;
            try
            {
               
$email = $this->authenticatedEmail( $accessToken['access_token'] );
            }
            catch ( \
Exception $e ) {}
           
            try
            {
                if (
$login->type === \IPS\Login::LOGIN_UCP )
                {
                   
$exception = new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT );
                   
$exception->handler = $this;
                   
$exception->member = $login->reauthenticateAs;
                    throw
$exception;
                }
               
               
$member = $this->createAccount( $name, $email );
               
                \
IPS\Db::i()->replace( 'core_login_links', array(
                   
'token_login_method'    => $this->id,
                   
'token_member'            => $member->member_id,
                   
'token_identifier'        => $userId,
                   
'token_linked'            => 1,
                   
'token_access_token'    => $accessToken['access_token'],
                   
'token_expires'            => isset( $accessToken['expires_in'] ) ? ( time() + intval( $accessToken['expires_in'] ) ) : NULL,
                   
'token_refresh_token'    => isset( $accessToken['refresh_token'] ) ? $accessToken['refresh_token'] : NULL,
                   
'token_scope'            => $scope ? json_encode( $scope ) : NULL,
                ) );
               
               
$member->logHistory( 'core', 'social_account', array(
                   
'service'        => static::getTitle(),
                   
'handler'        => $this->id,
                   
'account_id'    => $userId,
                   
'account_name'    => $name,
                   
'linked'        => TRUE,
                   
'registered'    => TRUE
               
) );
               
                if (
$syncOptions = $this->syncOptions( $member, TRUE ) )
                {
                   
$profileSync = array();
                    foreach (
$syncOptions as $option )
                    {
                       
$profileSync[ $option ] = array( 'handler' => $this->id, 'ref' => NULL, 'error' => NULL );
                    }
                   
$member->profilesync = $profileSync;
                   
$member->save();
                }
               
                return
$member;
            }
            catch ( \
IPS\Login\Exception $exception )
            {
                if (
$exception->getCode() === \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT )
                {
                    \
IPS\Db::i()->replace( 'core_login_links', array(
                       
'token_login_method'    => $this->id,
                       
'token_member'            => $exception->member->member_id,
                       
'token_identifier'        => $userId,
                       
'token_linked'            => 0,
                       
'token_access_token'    => $accessToken['access_token'],
                       
'token_expires'            => isset( $accessToken['expires_in'] ) ? ( time() + intval( $accessToken['expires_in'] ) ) : NULL,
                       
'token_refresh_token'    => isset( $accessToken['refresh_token'] ) ? $accessToken['refresh_token'] : NULL,
                       
'token_scope'            => $scope ? json_encode( $scope ) : NULL,
                    ) );
                }
               
                throw
$exception;
            }
        }
    }
   
   
/**
     * Exchange authorization code for access token
     *
     * @param    string    $code    Authorization code
     * @return    array
     * @throws    \IPS\Login\Exception
     */
   
protected function _exchangeAuthorizationCodeForAccessToken( $code )
    {
       
/* Make the request */
       
$data = NULL;
       
$response = array();
        try
        {
           
$data = $this->_authenticatedRequest( $this->tokenEndpoint(), array(
               
'grant_type'    => 'authorization_code',
               
'code'            => $code,
               
'redirect_uri'    => (string) $this->redirectionEndpoint(),
            ) );
           
$response = $data->decodeJson();
        }
        catch( \
RuntimeException $e )
        {
            \
IPS\Log::log( var_export( $data, true ), 'oauth' );
        }
       
       
/* Check for any errors */
       
if ( isset( $response['error'] ) or !isset( $response['access_token'] ) or ( isset( $response['token_type'] ) and mb_strtolower( $response['token_type'] ) !== 'bearer' ) )
        {
            \
IPS\Log::log( print_r( $response, TRUE ), 'oauth' );
            throw new \
IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
        }        
       
       
/* Return */
       
return $response;
    }
   
   
/**
     * Get link
     *
     * @param    \IPS\Member    $member    Member
     * @return    array
     */
   
protected function _link( \IPS\Member $member )
    {
       
$link = parent::_link( $member );

        if (
$link and $link['token_expires'] and $link['token_expires'] < time() and $link['token_refresh_token'] )
        {
            try
            {
               
$newAccessToken =  $this->_authenticatedRequest( $this->tokenEndpoint(), array(
                   
'grant_type'    => 'refresh_token',
                   
'refresh_token'    => $link['token_refresh_token'],
                ) )->
decodeJson();
               
                if ( isset(
$newAccessToken['error'] ) or !isset( $newAccessToken['access_token'] ) or ( isset( $newAccessToken['token_type'] ) and mb_strtolower( $newAccessToken['token_type'] ) !== 'bearer' ) )
                {
                    \
IPS\Log::log( print_r( $newAccessToken, TRUE ), 'oauth' );
                    \
IPS\Db::i()->update( 'core_login_links', array( 'token_refresh_token' => NULL ), array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) );
                    return
$link;
                }
               
               
$update = array();
                if ( isset(
$newAccessToken['access_token'] ) )
                {
                   
$update['token_access_token'] = $newAccessToken['access_token'];
                }
                if ( isset(
$newAccessToken['expires_in'] ) )
                {
                   
$update['token_expires'] = ( time() + $newAccessToken['expires_in'] );
                }
                if ( isset(
$newAccessToken['refresh_token'] ) )
                {
                   
$update['token_refresh_token'] = $newAccessToken['refresh_token'];
                }
               
                foreach (
$update as $k => $v )
                {
                   
$link[ $k ] = $v;
                   
$this->_cachedLinks[ $member->member_id ][ $k ] = $v;
                }
                \
IPS\Db::i()->update( 'core_login_links', $update, array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) );
            }
            catch ( \
Exception $e )
            {
                \
IPS\Log::log( $e, 'oauth' );
            }
        }
       
        return
$link;
    }
       
   
/* !OAuth Abstract */
   
    /**
     * Grant Type
     *
     * @return    string
     */
   
abstract protected function grantType();
   
   
/**
     * Get scopes to request
     *
     * @param    array|NULL    $additional    Any additional scopes to request
     * @return    array
     */
   
protected function scopesToRequest( $additional=NULL )
    {
        return array();
    }
   
   
/**
     * Scopes Issued
     *
     * @param    string        $accessToken    Access Token
     * @return    array|NULL
     */
   
public function scopesIssued( $accessToken )
    {
        return
$this->scopesToRequest(); // Unless the individual handler overrides this, we'll just assume it's given us what we asked for (which is how the OAuth spec says you're supposed to do it anyway)
   
}
   
   
/**
     * Authorized scopes
     *
     * @return    array|NULL
     */
   
public function authorizedScopes( \IPS\Member $member )
    {
        if ( !(
$link = $this->_link( $member ) ) )
        {
            return
NULL;
        }
                       
        return
$link['token_scope'] ? json_decode( $link['token_scope'] ) : NULL;
    }
   
   
/**
     * Authorization Endpoint
     *
     * @param    \IPS\Login    $login    The login object
     * @return    \IPS\Http\Url
     */
   
abstract protected function authorizationEndpoint( \IPS\Login $login );
   
   
/**
     * Token Endpoint
     *
     * @return    \IPS\Http\Url
     */
   
abstract protected function tokenEndpoint();
   
   
/**
     * Redirection Endpoint
     *
     * @return    \IPS\Http\Url
     */
   
protected function redirectionEndpoint()
    {
        return \
IPS\Http\Url::internal( 'oauth/callback/', 'none' );
    }
   
   
/**
     * Get authenticated user's identifier (may not be a number)
     *
     * @param    string    $accessToken    Access Token
     * @return    string
     */
   
abstract protected function authenticatedUserId( $accessToken );
   
   
/**
     * Get authenticated user's username
     * May return NULL if server doesn't support this
     *
     * @param    string    $accessToken    Access Token
     * @return    string|NULL
     */
   
protected function authenticatedUserName( $accessToken )
    {
        return
NULL;
    }
   
   
/**
     * Get authenticated user's email address
     * May return NULL if server doesn't support this
     *
     * @param    string    $accessToken    Access Token
     * @return    string|NULL
     */
   
protected function authenticatedEmail( $accessToken )
    {
        return
NULL;
    }
   
   
/**
     * Get user's identifier (may not be a number)
     * May return NULL if server doesn't support this
     *
     * @param    \IPS\Member    $member    Member
     * @return    string|NULL
     * @throws    \IPS\Login\Exception    The token is invalid and the user needs to reauthenticate
     * @throws    \DomainException        General error where it is safe to show a message to the user
     * @throws    \RuntimeException        Unexpected error from service
     */
   
public function userId( \IPS\Member $member )
    {
        if ( !(
$link = $this->_link( $member ) ) )
        {
            throw new \
IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
        }
       
        return
$this->authenticatedUserId( $link['token_access_token'] );
    }
   
   
/**
     * Get user's profile name
     * May return NULL if server doesn't support this
     *
     * @param    \IPS\Member    $member    Member
     * @return    string|NULL
     * @throws    \IPS\Login\Exception    The token is invalid and the user needs to reauthenticate
     * @throws    \DomainException        General error where it is safe to show a message to the user
     * @throws    \RuntimeException        Unexpected error from service
     */
   
public function userProfileName( \IPS\Member $member )
    {
        if ( !(
$link = $this->_link( $member ) ) )
        {
            throw new \
IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
        }
       
        return
$this->authenticatedUserName( $link['token_access_token'] );
    }
   
   
/**
     * Get user's email address
     * May return NULL if server doesn't support this
     *
     * @param    \IPS\Member    $member    Member
     * @return    string|NULL
     * @throws    \IPS\Login\Exception    The token is invalid and the user needs to reauthenticate
     * @throws    \DomainException        General error where it is safe to show a message to the user
     * @throws    \RuntimeException        Unexpected error from service
     */
   
public function userEmail( \IPS\Member $member )
    {
        if ( !(
$link = $this->_link( $member ) ) )
        {
            throw new \
IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
        }
       
        return
$this->authenticatedEmail( $link['token_access_token'] );
    }
   
   
/* !UCP */
   
    /**
     * 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 )
    {
        if ( !isset(
$this->settings['show_in_ucp'] ) ) // Default to showing
       
{
            return
TRUE;
        }
        return
parent::showInUcp( $member );
    }
}