* start() * $session = new ezcAuthenticationSession(); * $session->start(); * * // $token is used as a key in the session to store the authenticated state between requests * $token = isset( $_GET['token'] ) ? $_GET['token'] : $session->load(); * * $credentials = new ezcAuthenticationIdCredentials( $token ); * $authentication = new ezcAuthentication( $credentials ); * $authentication->session = $session; * * $filter = new ezcAuthenticationTypekeyFilter(); * $authentication->addFilter( $filter ); * // add other filters if needed * * if ( !$authentication->run() ) * { * // authentication did not succeed, so inform the user * $status = $authentication->getStatus(); * $err = array( * 'ezcAuthenticationTypekeyFilter' => array( * ezcAuthenticationTypekeyFilter::STATUS_SIGNATURE_INCORRECT => 'Signature returned by TypeKey is incorrect', * ezcAuthenticationTypekeyFilter::STATUS_SIGNATURE_EXPIRED => 'The signature returned by TypeKey expired' * ), * 'ezcAuthenticationSession' => array( * ezcAuthenticationSession::STATUS_EMPTY => '', * ezcAuthenticationSession::STATUS_EXPIRED => 'Session expired' * ) * ); * foreach ( $status as $line ) * { * list( $key, $value ) = each( $line ); * echo $err[$key][$value] . "\n"; * } * ?> * *
* Logged-in"; * } * ?> * * * Another method, which doesn't use JavaScript, is using an intermediary page * which saves the token in the session, then calls the TypeKey login page: * * - original file is modified as follows: *
*
*
*
* - intermediary page:
*
* start()
* $session = new ezcAuthenticationSession();
* $session->start();
*
* // $token is used as a key in the session to store the authenticated state between requests
* $token = isset( $_GET['t'] ) ? $_GET['t'] : $session->load();
* if ( $token !== null )
* {
* $session->save( $token );
* }
* $url = isset( $_GET['_return'] ) ? $_GET['_return'] : null;
* $url .= "?token={$token}";
* header( "Location: https://www.typekey.com/t/typekey/login?t={$token}&_return={$url}" );
* ?>
*
*
* Extra data can be fetched from the TypeKey server during the authentication
* process. Different from the other filters, for TypeKey there is no registration
* needed for fetching the extra data, because all the possible extra data is
* available in the response sent by the TypeKey server.
*
* To be able to get the email address of the user, need_email must be set
* to a value different than 0 in the initial request sent to the TypeKey
* server (along with the t and _return values). Example:
* - https://www.typekey.com/t/typekey/login?t=
* // after run()
* // $filter is an ezcAuthenticationTypekeyFilter object
* $data = $filter->fetchData();
*
*
* The $data array contains name (TypeKey username), nick (TypeKey display name)
* and optionally email (if the user allowed the sharing of his email address
* in the TypeKey profile page; otherwise it is not set).
*
* array( 'name' => array( 'john' ),
* 'nick' => array( 'John Doe' ),
* 'email' => array( 'john.doe@example.com' ) // or not set
* );
*
*
* @property ezcAuthenticationBignumLibrary $lib
* The wrapper for the PHP extension to use for big number operations.
* This will be autodetected in the constructor, but you can specify
* your own wrapper before calling run().
*
* @package Authentication
* @version 1.2
* @mainclass
*/
class ezcAuthenticationTypekeyFilter extends ezcAuthenticationFilter implements ezcAuthenticationDataFetch
{
/**
* The request does not contain the needed information (like $_GET['sig']).
*/
const STATUS_SIGNATURE_MISSING = 1;
/**
* Signature verification was incorect.
*/
const STATUS_SIGNATURE_INCORRECT = 2;
/**
* Login is outside of the timeframe.
*/
const STATUS_SIGNATURE_EXPIRED = 3;
/**
* Holds the extra data fetched during the authentication process.
*
* Contains name (TypeKey username), nick (TypeKey display name) and
* optionally email (if the user allowed the sharing of his email address
* in the TypeKey profile page; otherwise it is not set).
*
* Usually it has this structure:
*
* array( 'name' => array( 'john' ),
* 'nick' => array( 'John Doe' ),
* 'email' => array( 'john.doe@example.com' ) // or not set
* );
*
*
* @var array(string=>mixed)
*/
protected $data = array();
/**
* Holds the properties of this class.
*
* @var array(string=>mixed)
*/
private $properties = array();
/**
* Creates a new object of this class.
*
* @throws ezcBaseExtensionNotFoundException
* if neither of the PHP gmp and bcmath extensions are installed
* @param ezcAuthenticationTypekeyOptions $options Options for this class
*/
public function __construct( ezcAuthenticationTypekeyOptions $options = null )
{
$this->options = ( $options === null ) ? new ezcAuthenticationTypekeyOptions() : $options;
$this->lib = ezcAuthenticationMath::createBignumLibrary();
}
/**
* Sets the property $name to $value.
*
* @throws ezcBasePropertyNotFoundException
* if the property $name does not exist
* @throws ezcBaseValueException
* if $value is not correct for the property $name
* @param string $name The name of the property to set
* @param mixed $value The new value of the property
* @ignore
*/
public function __set( $name, $value )
{
switch ( $name )
{
case 'lib':
if ( $value instanceof ezcAuthenticationBignumLibrary )
{
$this->properties[$name] = $value;
}
else
{
throw new ezcBaseValueException( $name, $value, 'ezcAuthenticationBignumLibrary' );
}
break;
default:
throw new ezcBasePropertyNotFoundException( $name );
}
}
/**
* Returns the value of the property $name.
*
* @throws ezcBasePropertyNotFoundException
* if the property $name does not exist
* @param string $name The name of the property for which to return the value
* @return mixed
* @ignore
*/
public function __get( $name )
{
switch ( $name )
{
case 'lib':
return $this->properties[$name];
default:
throw new ezcBasePropertyNotFoundException( $name );
}
}
/**
* Returns true if the property $name is set, otherwise false.
*
* @param string $name The name of the property to test if it is set
* @return bool
* @ignore
*/
public function __isset( $name )
{
switch ( $name )
{
case 'lib':
return isset( $this->properties[$name] );
default:
return false;
}
}
/**
* Runs the filter and returns a status code when finished.
*
* @throws ezcAuthenticationTypekeyException
* if the keys from the TypeKey public keys file could not be fetched
* @param ezcAuthenticationIdCredentials $credentials Authentication credentials
* @return int
*/
public function run( $credentials )
{
$source = $this->options->requestSource;
if ( isset( $source['name'] ) && isset( $source['email'] ) && isset( $source['nick'] ) && isset( $source['ts'] ) && isset( $source['sig'] ) )
{
// parse the response URL sent by the TypeKey server
$id = isset( $source['name'] ) ? $source['name'] : null;
$mail = isset( $source['email'] ) ? $source['email'] : null;
$nick = isset( $source['nick'] ) ? $source['nick'] : null;
$timestamp = isset( $source['ts'] ) ? $source['ts'] : null;
$signature = isset( $source['sig'] ) ? $source['sig'] : null;
// extra data which will be returned by fetchData()
$this->data['name'] = array( $id );
$this->data['nick'] = array( $nick );
if ( strpos( $mail, '@' ) !== false )
{
$this->data['email'] = array( $mail );
}
}
else
{
return self::STATUS_SIGNATURE_MISSING;
}
if ( $this->options->validity !== 0 &&
time() - $timestamp >= $this->options->validity )
{
return self::STATUS_SIGNATURE_EXPIRED;
}
$keys = $this->fetchPublicKeys( $this->options->keysFile );
$msg = "{$mail}::{$id}::{$nick}::{$timestamp}";
$signature = rawurldecode( urlencode( $signature ) );
list( $r, $s ) = explode( ':', $signature );
if ( $this->checkSignature( $msg, $r, $s, $keys ) )
{
return self::STATUS_OK;
}
return self::STATUS_SIGNATURE_INCORRECT;
}
/**
* Checks the information returned by the TypeKey server.
*
* @param string $msg Plain text signature which needs to be verified
* @param string $r First part of the signature retrieved from TypeKey
* @param string $s Second part of the signature retrieved from TypeKey
* @param array(string=>string) $keys Public keys retrieved from TypeKey
* @return bool
*/
protected function checkSignature( $msg, $r, $s, $keys )
{
$lib = $this->lib;
$r = base64_decode( $r );
$s = base64_decode( $s );
foreach ( $keys as $key => $value )
{
$keys[$key] = $lib->init( (string) $value );
}
$s1 = $lib->init( $lib->binToDec( $r ) );
$s2 = $lib->init( $lib->binToDec( $s ) );
$w = $lib->invert( $s2, $keys['q'] );
$msg = $lib->hexToDec( sha1( $msg ) );
$u1 = $lib->mod( $lib->mul( $msg, $w ), $keys['q'] );
$u2 = $lib->mod( $lib->mul( $s1, $w ), $keys['q'] );
$v = $lib->mul( $lib->powmod( $keys['g'], $u1, $keys['p'] ), $lib->powmod( $keys['pub_key'], $u2, $keys['p'] ) );
$v = $lib->mod( $lib->mod( $v, $keys['p'] ), $keys['q'] );
return ( $lib->cmp( $v, $s1 ) === 0 );
}
/**
* Fetches the public keys from the specified file or URL $file.
*
* The file must be composed of space-separated values for p, g, q, and
* pub_key, like this:
* p=
* array( 'p' => p_val, 'g' => g_val, 'q' => q_val, 'pub_key' => pub_key_val )
*
*
* @throws ezcAuthenticationTypekeyException
* if the keys from the TypeKey public keys file could not be fetched
* @param string $file The public keys file or URL
* @return array(string=>string)
*/
protected function fetchPublicKeys( $file )
{
$data = @file_get_contents( $file );
if ( empty( $data ) )
{
throw new ezcAuthenticationTypekeyException( "Could not fetch public keys from '{$file}'." );
}
$lines = explode( ' ', trim( $data ) );
foreach ( $lines as $line )
{
$val = explode( '=', $line );
if ( count( $val ) < 2 )
{
throw new ezcAuthenticationTypekeyException( "The data retrieved from '{$file}' is invalid." );
}
$keys[$val[0]] = $val[1];
}
return $keys;
}
/**
* Registers the extra data which will be fetched by the filter during the
* authentication process.
*
* For TypeKey there is no registration needed, because all the possible
* extra data is available in the response sent by the TypeKey server. So
* a call to this function is not needed.
*
* To be able to get the email address of the user, need_email must be set
* to a value different than 0 in the initial request sent to the TypeKey
* server (along with the t and _return values).
*
* @param array(string) $data A list of attributes to fetch during authentication
*/
public function registerFetchData( array $data = array() )
{
// does not need to do anything because all the extra data is returned by default
}
/**
* Returns the extra data which was fetched during the authentication process.
*
* The TypeKey extra data is an array containing the values for name (the
* TypeKey username), nick (the TypeKey display name) and email (the email
* address of the user, fetched only if the initial request to the TypeKey
* server contains need_email, and the user allowed the sharing of his email
* address).
*
* Example of returned array:
*
* array( 'name' => array( 'john' ),
* 'nick' => array( 'John Doe' ),
* 'email' => array( 'john.doe@example.com' ) // or not set
* );
*
*
* @return array(string=>mixed)
*/
public function fetchData()
{
return $this->data;
}
}
?>