<?php
// vim: foldmethod=marker
/**
 * test and example - Auth_Typekey Crypt_DSA version
 *
 * @author TSURUOKA Naoya <tsuruoka@labs.cybozu.co.jp>
 */

require_once 'PEAR.php';
require_once 
'lime/lib/lime.php';
require_once 
'../Crypt/DSA.php';

/**
* Error code
*/
define('AUTH_TYPEKEY_ERROR_INVALID', -1);
define('AUTH_TYPEKEY_ERROR_TIMEOUT', -2);
define('AUTH_TYPEKEY_ERROR_NOT_RETRIEVE_PUBKEY', -4);
define('AUTH_TYPEKEY_ERROR_NOT_SET_SITE_TOKEN', -5);
define('AUTH_TYPEKEY_ERROR_NOT_EXIST_GMP', -6);
define('AUTH_TYPEKEY_ERROR_NOT_EXIST_BCMATH', -7);

/**
 * Copyright (c) 2004 Daiji Hriata All Right Reserved.
 * $Id: Auth_TypeKey.phps,v 1.3 2004/10/10 14:59:49 hirata Exp $
 *
 * Auth_TypeKey Class
 *
 * Simple example:
 *
 * $tk = new Auth_TypeKey();
 * $tk->site_token('PUTYOURTYPEKEYTOKEN');
 * $tk->verifyTypeKey( $msg );
 * if (PEAR::isError($result)) {
 *     echo "INVALID";
 * } else {
 *     echo "VALID";
 * }
 *
 * @see http://uva.jp/dh/mt/archives/004529.html
 */
class Auth_TypeKey_Custom extends PEAR
{
    
//{{{ class member variables
    /**
     * TypeKey Public_Key URLs
     */ 
    
var $AUTH_TYPEKEY_BASEURL 'https://www.typekey.com/t/typekey/';
    var 
$AUTH_TYPEKEY_SIG_URL 'http://www.typekey.com/extras/regkeys.txt';

    
/**
     * TypeKey version
     */
    
var $AUTH_TYPEKEY_VERSION '1';

    
/**
     * TypeKey requires site_token
     */
    
var $AUTH_TYPEKEY_SITE_TOKEN NULL;


    
/**
     * time limitatino of TypeKey validation (sec)
     */
    
var $AUTH_TYPEKEY_TIMEOUT 300;

    
/**
     * function to compute BIGINT
     */
    
var $AUTH_TYPEKEY_BIGINT '';
    
//}}}

    //{{{ Auth_TypeKey
    /**
     * Initialize.
     *
     * @param array $init        parameters for initialize: 
     *                          'version' : TypeKey version
     *                          'token' : TypeKey Site Token
     *
     * @return mixed  true on success. PEAR_Error on failure.
     * 
     * @access public
     */
    
function Auth_TypeKey_Custom($init = array())
    {
        
$this->PEAR();
        if (
array_key_exists('version'$init)) {
            
$this->version($init['version']);
        }
        if (
array_key_exists('token'$init)) {
            
$this->site_token($init['token']);
        }
        return 
true;
    }
    
//}}}

    //{{{ version
    /**
     * Set/show TypeKey version
     *
     * @param  string $version   TypeKey version to set.  
     *                           If nothing, just look.
     *
     * @return string            Current setting of TypeKey version
     * 
     * @access public
     */
    
function version($version NULL)
    {
        if (!
is_null($version)) {
            
$this->AUTH_TYPEKEY_VERSION $version;
        }
        return 
$this->AUTH_TYPEKEY_VERSION;
    }
    
//}}}

    //{{{ site_token
    /**
     * Set/show TypeKey Site Token
     *
     * @param  string $version        TypeKey Site Token to set.  
     *                                if nothing, just look.
     *
     * @return mixed  string          Current setting of TypeKey Site Token 
     *                                if set. PEAR_Error if not set.
     * 
     * @access public
     */
    
function site_token($token NULL
    {
        if (!
is_null($token)) {
            
$this->AUTH_TYPEKEY_SITE_TOKEN $token;
        }
        if (
is_null($this->AUTH_TYPEKEY_SITE_TOKEN)) {
            return 
PEAR::raiseError('SITE TOKEN is not set.',
                                     
AUTH_TYPEKEY_ERROR_NOT_SET_SITE_TOKEN);
        }
        return 
$this->AUTH_TYPEKEY_SITE_TOKEN;
    }
    
//}}}

    //{{{ verifyTypeKey
    /**
     * Verify the TypeKey authentication requirement
     *
     * @param  array $query        TypeKey Site Token to set.  if nothing, just look.
     * @param  array $key          Public Keys of TypeKey service.  if NULL, using preset URLs.
     *
     * @return mixed  true on success.  PEAR_Error on failure.
     * 
     * @access public
     */
    
function verifyTypeKey $query,  $key NULL 
    {
        
// Retrieve key per each request.
        
if (is_null($key)) {
            
$key $this->_fetch_key('');
            if (
$key == false) {
                return 
PEAR::raiseError('Cannot get pubkey.',
                                        
AUTH_TYPEKEY_ERROR_NOT_RETRIEVE_PUBKEY);
            }
        }

        foreach ( array( 
'email''name''nick''ts''sig') as $i ) {
            $
$i $query[$i];
        }
        
        
// The nickname field is sent as url_encoded utf-8 data(like %xx%xx... )
        // from Tyepkey but signed before encoded.  Usually all fields are
        // decoded by PHP automatically, but it might cause invalid_error,
        // especially using with mb_stirng.  rawurlencoded strings is
        // better for that case.  It can be gotten from QUERY_STRING
        
$nick rawurldecode($nick);
        
$sig str_replace(' ''+'$sig);
        
$email str_replace(' ''+'$email);

        switch (
$this->version()) {
        case 
'1':
            
$message implode('::', array( $email$name$nick$ts));
            break;
        case 
'1.1':
          
$token $this->site_token();
          if (
PEAR::isError($token)) {
            return 
$token;
          }                
          
$message implode('::', array( $email$name$nick$ts$token));
                break;
        default:
          
// default is version '1'
          
$message implode('::', array( $email$name$nick$ts));
          break;
        }
        
        if (
Crypt_DSA::verify($message$sig$key) == true) {
            if ( 
time() - $ts $this->AUTH_TYPEKEY_TIMEOUT ) {
                return 
PEAR::raiseError("Timestamp from TypeKey is too old"
                                        
AUTH_TYPEKEY_ERROR_TIMEOUT);
            }
            return 
true;
        } else {
            return 
PEAR::raiseError("Invalid signature"
                                    
AUTH_TYPEKEY_ERROR_INVALID);
        }
    }
    
//}}}

    //{{{ urlSignIn
    /* Generate URL to link to Sign-In using TypeKey service
     * 
     * @param  string  $return_url        URL to be returned after authentication
     * @param  boolean $need_email        if true, require email address of user.
     *
     * @return mixed   string  $url                URL to link TypeKey Sign-In
     *                 PEAR_Error on failure
     *
     * @access public
     */

    
function urlSignIn ($return_url$need_email false$options = array ())
    {
        
$token $this->site_token();
        if (
PEAR::isError($token)) {
            return 
$token;
        }

        
$option_query '';
        foreach (
$options as $key => $value) {
            
$option_query .= '&' $key '=' $value;
        }

        
$url $this->AUTH_TYPEKEY_BASEURL;
        
$url .= "login?t=" $token;
        
$url .= (($need_email == 1) ? "&need_email=1" '');
        
$url .= (($this->version()) ? "&v=" $this->version() : '');
        
$url .= $option_query;
        
$url .= "&_return=" rawurlencode($return_url);
        return 
$url;
    }    
    
//}}}

    //{{{ urlSignOut
    /* Generate URL to link to sign out
     * 
     * @param  string  $return_url        URL to be returned after authentication
     *
     * @return string  $url                URL to link TypeKey sign out.
     *
     * @access public
     */
    
function urlSignOut $return_url ) {
        
$url $this->AUTH_TYPEKEY_BASEURL;
        
$url .= "logout?";
        
$url .= "_return=" rawurlencode($return_url);
        return 
$url;
    }    
    
//}}}

    //{{{ _fetch_key
    /* 
     * Fetch keys of TypeKey service
     * 
     * @param  string $url        URL of TypeKey Public Keys
     *
     * @return mixed  array of Public keys of TypeKey services
     *                false on error
     *
     * @access private
     */
    
function _fetch_key $url '')
    {
        if (
$url == '') {
            
$url $this->AUTH_TYPEKEY_SIG_URL;
        }
        
$lines = @file($url);
        if (
$lines == false) {
            return 
false;
        }
        
$key_raw explode(" "$lines[0]);

        foreach (
$key_raw as $e) {
            list(
$key_index$key_value) = explode("="$e);
            
$key[$key_index] = trim($key_value);
        }
        return 
$key;
    }
    
//}}}
}

$t = new lime_test(null, new lime_output_color);

$cache_filename dirname(__FILE__) . DIRECTORY_SEPARATOR "test_value.txt";
$token "IFyUFePnQ9pn6DTxMdYs";
$query unserialize(file_get_contents($cache_filename));

$tk_config = array(
    
'token' => $token,
    
'version' => '1.1',
);
$tk = new Auth_TypeKey_Custom($tk_config);
$tk->AUTH_TYPEKEY_TIMEOUT time();

$t->ok(is_object($tk), "make instance");

$result $tk->verifyTypeKey($query);
if (
PEAR::isError($result)) {
    
var_dump($result->getMessage());
}
$t->is(falsePEAR::isError($result), "check PEAR::isError");
?>