Seditio Source
Root |
./othercms/ips_4.3.4/oauth/token/index.php
<?php
/**
 * @brief        OAuth Server Generate Access Token Endpoint
 * @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        29 Apr 2017
 */

define('REPORT_EXCEPTIONS', TRUE);
require
'../../init.php';

class
oAuthServerTokenRequest
{
   
/**
     * @brief    Client
     */
   
public $client;
   
   
/**
     * Init
     *
     * @param    string        $clientId        Client ID
     * @param    string        $clientSecret    Client Secret
     * @return    void
     */
   
public static function init( $clientId, $clientSecret )
    {
       
$obj = new static;
       
       
/* Get the client */
       
try
        {
           
$obj->client = \IPS\Api\OAuthClient::load( $clientId );
            if ( !
$obj->client->enabled )
            {
                throw new \
OutOfRangeException;
            }
        }
        catch ( \
OutOfRangeException $e )
        {
            throw new \
IPS\Login\Handler\OAuth2\Exception( 'invalid_client' );
        }
       
       
/* Validate the secret */
       
if ( $obj->client->client_secret )
        {
           
$bruteForce = $obj->client->brute_force ? json_decode( $obj->client->brute_force, TRUE ) : array();
           
            if ( isset(
$bruteForce[ \IPS\Request::i()->ipAddress() ] ) and $bruteForce[ \IPS\Request::i()->ipAddress() ] >= 3 )
            {
                throw new \
IPS\Login\Handler\OAuth2\Exception( 'invalid_client', "blocked for too many authentication failures" );
            }
           
            if (
password_verify( $clientSecret, $obj->client->client_secret ) )
            {
                if ( isset(
$bruteForce[ \IPS\Request::i()->ipAddress() ] ) )
                {
                    unset(
$bruteForce[ \IPS\Request::i()->ipAddress() ] );
                   
$obj->client->brute_force = json_encode( $bruteForce );
                   
$obj->client->save();
                }
            }
            else
            {            
                if ( !isset(
$bruteForce[ \IPS\Request::i()->ipAddress() ] ) )
                {
                   
$bruteForce[ \IPS\Request::i()->ipAddress() ] = 0;
                }
               
$bruteForce[ \IPS\Request::i()->ipAddress() ]++;
               
$obj->client->brute_force = json_encode( $bruteForce );
               
$obj->client->save();
               
                throw new \
IPS\Login\Handler\OAuth2\Exception( 'invalid_client' );
            }
        }
       
        return
$obj;
    }
   
   
/**
     * Validate the grant type
     *
     * @param    string    $grantType    The Authorization Code
     * @return    string
     */
   
public function grantType( $grantType )
    {
        if ( !
in_array( $grantType, array( 'authorization_code', 'implicit', 'client_credentials', 'password', 'refresh_token' ) ) )
        {
            throw new \
IPS\Login\Handler\OAuth2\Exception( 'unsupported_grant_type' );
        }
       
        if (
$grantType === 'refresh_token' )
        {
            if ( !
$this->client->use_refresh_tokens )
            {
                throw new \
IPS\Login\Handler\OAuth2\Exception( 'unsupported_grant_type' );
            }
        }
        elseif ( !
in_array( $grantType, explode( ',', $this->client->grant_types ) ) )
        {
            throw new \
IPS\Login\Handler\OAuth2\Exception( 'unauthorized_client' );
        }
       
        return
$grantType;
    }

   
   
/**
     * Validate Authorization Code
     *
     * @param    string    $authorizationCode    The Authorization Code
     * @return    array|NULL
     */
   
public function validateAuthorizationCode( $authorizationCode, $redirectUri = NULL )
    {
        try
        {
           
$authorizationCode = \IPS\Db::i()->select( '*', 'core_oauth_server_authorization_codes', array( 'client_id=? AND code=?', $this->client->client_id, $authorizationCode ) )->first();
           
           
/* If it's expired, delete it and do not validate */
           
if ( $authorizationCode['expires'] < time() )
            {
                \
IPS\Db::i()->delete( 'core_oauth_server_authorization_codes', array( 'client_id=? AND code=?', $authorizationCode['client_id'], $authorizationCode['code'] ) );
                return;
            }
           
           
/* If it's already been used, this should be treated as an attack: revoke any access tokens already generated and do not validate */
           
if ( $authorizationCode['used'] )
            {
                \
IPS\Db::i()->delete( 'core_oauth_server_access_tokens', array( 'client_id=? AND authorization_code=?', $this->client->client_id, $authorizationCode['code'] ) );
                return;
            }
           
           
/* If the redirect URI does not match, do not validate */    
           
if ( $redirectUri !== $authorizationCode['redirect_uri'] )
            {
                return;
            }
           
           
/* Mark it used */
           
\IPS\Db::i()->update( 'core_oauth_server_authorization_codes', array( 'used' => 1 ), array( 'client_id=? AND code=?', $authorizationCode['client_id'], $authorizationCode['code'] ) );
           
           
/* Return access token */
           
return $this->client->generateAccessToken( \IPS\Member::load( $authorizationCode['member_id'] ), $authorizationCode['scope'] ? json_decode( $authorizationCode['scope'] ) : NULL, 'authorization_code', FALSE, $authorizationCode['code'] );
        }
        catch ( \
UnderflowException $e )
        {
            return;
        }
    }
   
   
/**
     * Validate Password
     *
     * @param    string        $username    Username
     * @param    string        $password    Password
     * @param    array|null    $scope        Scopes
     * @return    array|NULL
     */
   
public function validatePassword( $username, $password, $scope )
    {
       
$member = NULL;
       
$accessToken = NULL;
       
$fails = array();
               
       
$login = new \IPS\Login();
               
        foreach (
$login->usernamePasswordMethods() as $method )
        {
            try
            {
               
$member = $method->authenticateUsernamePassword( $login, $username, $password );
                \
IPS\Login::checkIfAccountIsLocked( $member, TRUE );
               
               
$accessToken = $this->client->generateAccessToken( $member, $scope, 'password' );
                break;
            }
            catch ( \
IPS\Login\Exception $e )
            {
                if (
$e->getCode() === \IPS\Login\Exception::BAD_PASSWORD and $e->member )
                {
                   
$fails[ $e->member->member_id ] = $e->member;
                }
            }
            catch ( \
Exception $e ) { }
        }
               
        foreach (
$fails as $failedMember )
        {
            if ( !
$member or $failedMember->member_id != $failedMember->member_id )
            {
               
$failedLogins = is_array( $failedMember->failed_logins ) ? $failedMember->failed_logins : array();
               
$failedLogins[ \IPS\Request::i()->ipAddress() ][] = time();
               
$failedMember->failed_logins = $failedLogins;
               
$failedMember->save();
            }
        }
       
        return
$accessToken;
    }
   
   
/**
     * Validate Client Credentials
     *
     * @param    array|null    $scope        Scopes
     * @return    array|NULL
     */
   
public function validateClientCredentials( $scope )
    {
        if (
$this->client->client_secret )
        {
            return
$this->client->generateAccessToken( NULL, $scope, 'client_credentials', TRUE );
        }
    }
   
   
/**
     * Validate Refresh Token
     *
     * @param    string        $refreshToken    The refresh token
     * @param    array|null    $scope            Scopes
     * @return    array|NULL
     */
   
public function validateRefreshToken( $refreshToken, $newScope )
    {
       
$accessToken = $this->client->validateRefreshToken( $refreshToken );
        if (
$accessToken )
        {
           
$member = NULL;
            if (
$accessToken['member_id'] )
            {
               
$member = \IPS\Member::load( $accessToken['member_id'] );
                if ( !
$member->member_id )
                {
                    return;
                }
            }
           
           
$originalScope = $accessToken['scope'] ? json_decode( $accessToken['scope'], TRUE ) : array();
           
$scope = $newScope ? array_intersect( $originalScope, $newScope ) : $originalScope;
           
            return
$this->client->generateAccessToken( $member, $scope, 'refresh_token', TRUE );
        }
    }
}

/* Handle it */
try
{
   
/* TLS only */
   
if ( \IPS\OAUTH_REQUIRES_HTTPS and !\IPS\Request::i()->isSecure() )
    {
        throw new \
IPS\Login\Handler\OAuth2\Exception( 'invalid_request', "request must be made with https" );
    }
   
   
/* POST only */
   
if ( \IPS\Request::i()->requestMethod() !== 'POST' )
    {
        throw new \
IPS\Login\Handler\OAuth2\Exception( 'invalid_request', "request must be a POST request" );
    }
   
   
/* Check we are not IP banned */
   
$ipBanned = \IPS\Request::i()->ipAddressIsBanned();
    if (
$ipBanned )
    {
        throw new \
IPS\Login\Handler\OAuth2\Exception( 'invalid_client', "IP Address banned" );
    }
   
   
/* Get the client id and secret */
   
$clientId = NULL;
   
$clientSecret = NULL;
    if ( isset(
$_POST['client_id'] ) )
    {
       
$clientId = $_POST['client_id'];
       
$clientSecret = isset( $_POST['client_secret'] ) ? $_POST['client_secret'] : NULL;
    }
    elseif ( isset(
$_SERVER['PHP_AUTH_USER'] ) )
    {
       
$clientId = $_SERVER['PHP_AUTH_USER'];
       
$clientSecret = isset( $_SERVER['PHP_AUTH_PW'] ) ? $_SERVER['PHP_AUTH_PW'] : NULL;
    }
    else
    {
        foreach (
$_SERVER as $k => $v )
        {
            if (
mb_substr( $k, -18 ) == 'HTTP_AUTHORIZATION' )
            {
               
$exploded = explode( ':', base64_decode( mb_substr( $v, 6 ) ) );
                if ( isset(
$exploded[0] ) and isset( $exploded[1] ) )
                {
                   
$clientId = $exploded[0];
                   
$clientSecret = isset( $exploded[1] ) ? $exploded[1] : NULL;
                }
            }
        }
    }

   
/* Initiate request */
   
$request = oAuthServerTokenRequest::init( $clientId, $clientSecret );
   
   
/* Validate grant */
   
$accessToken = NULL;
    switch (
$request->grantType( \IPS\Request::i()->grant_type ) )
    {
        case
'authorization_code':
           
$accessToken = $request->validateAuthorizationCode( \IPS\Request::i()->code, \IPS\Request::i()->redirect_uri );
            break;
        case
'password':
           
$accessToken = $request->validatePassword( \IPS\Request::i()->username, \IPS\Request::i()->password, isset( \IPS\Request::i()->scope ) ? explode( ' ', \IPS\Request::i()->scope ) : NULL );
            break;
        case
'client_credentials':
           
$accessToken = $request->validateClientCredentials( isset( \IPS\Request::i()->scope ) ? explode( ' ', \IPS\Request::i()->scope ) : NULL );
            break;
        case
'refresh_token':
           
$accessToken = $request->validateRefreshToken( \IPS\Request::i()->refresh_token, isset( \IPS\Request::i()->scope ) ? explode( ' ', \IPS\Request::i()->scope ) : NULL );
            break;
    }
   
   
/* Return */
   
if ( $accessToken )
    {        
       
$response = array( 'access_token' => $accessToken['access_token'], 'token_type' => 'bearer' );
        if (
$accessToken['access_token_expires'] )
        {
           
$response['expires_in'] = $accessToken['access_token_expires'] - time();
        }
        if (
$accessToken['refresh_token'] )
        {
           
$response['refresh_token'] = $accessToken['refresh_token'];
        }
        if (
$accessToken['scope'] )
        {    
           
$response['scope'] = implode( ' ', json_decode( $accessToken['scope'], TRUE ) );
        }
       
        \
IPS\Output::i()->sendOutput( json_encode( $response ), 200, 'application/json', array( 'Cache-Control' => 'no-store', 'Pragma' => 'no-cache' ), FALSE, FALSE, FALSE );
    }
    else
    {
        throw new \
IPS\Login\Handler\OAuth2\Exception( 'invalid_grant', 400 );
    }
}
catch ( \
IPS\Login\Handler\OAuth2\Exception $e )
{
   
$response = array( 'error' => $e->getMessage() );
    if (
$e->description )
    {
       
$response['error_description'] = $e->description;
    }
    \
IPS\Output::i()->sendOutput( json_encode( $response ), $e->getMessage() === 'invalid_client' ? 401 : 400, 'application/json', array(), FALSE, FALSE, FALSE );
}
catch (
Exception $e )
{
    \
IPS\Output::i()->sendOutput( json_encode( array( 'error' => 'server_error', 'error_description' => $e->getMessage() ) ), 500, 'application/json', array(), FALSE, FALSE, FALSE );
}