<?php
/**
 * icard_managed_lib.php
 *
 * Copyright (c) 2007, Robert Richards <rrichards@cdatazone.org>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *
 *   * Neither the name of Robert Richards nor the names of his
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @author     Robert Richards <rrichards@cdatazone.org>
 * @copyright  2007 Robert Richards <rrichards@cdatazone.org>
 * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License
 * @version    1.0.0
 */

class ICardEndpoint {
    private 
$address;
    private 
$secpolicy;
    private 
$identityCert = array('cert'=>NULL'isPem'=>True'isURL'=>False);

    const 
WSANS 'http://www.w3.org/2005/08/addressing';
    const 
WSAPFX 'wsa';
    
    const 
MEXNS 'http://schemas.xmlsoap.org/ws/2004/09/mex';
    const 
MEXPFX 'mex';
    
    const 
WSIDNS 'http://schemas.xmlsoap.org/ws/2006/02/addressingidentity';
    const 
WSIDPFX 'wsai';

    public function 
__construct($address$secpolicyAddress) {
        if (empty(
$address)) {
            throw new 
Exception("Address must be supplied");
        }
        if (empty(
$secpolicyAddress)) {
            throw new 
Exception("Security Policy Address must be supplied");
        }
        
$this->address $address;
        
$this->secpolicy $secpolicyAddress;
    }

    public function 
setIdentityX509($cert$isPEMFormat=TRUE$isURL=False) {
        
$this->identityCert['cert'] = $cert;
        
$this->identityCert['isPem'] = $isPEMFormat;
        
$this->identityCert['isURL'] = $isURL;
    }

    public function 
appendToDoc($parent) {
        if (! 
$parent instanceof DOMElement) {
            throw new 
Exception("Parent must be a DOMElement");
        }
        
$doc $parent->ownerDocument;
        
$endpoint $doc->createElementNS(self::WSANSself::WSAPFX.':EndpointReference');
        
$parent->appendChild($endpoint);

        
$addrNode $doc->createElementNS(self::WSANSself::WSAPFX.':Address'$this->address);
        
$endpoint->appendChild($addrNode);

        
$WSAMetaNode $doc->createElementNS(self::WSANSself::WSAPFX.':Metadata');
        
$endpoint->appendChild($WSAMetaNode);
        
        
$MEXNode $doc->createElementNS(self::MEXNSself::MEXPFX.':Metadata');
        
$WSAMetaNode->appendChild($MEXNode);
        
        
$MEXSection $doc->createElementNS(self::MEXNSself::MEXPFX.':MetadataSection');
        
$MEXNode->appendChild($MEXSection);
        
$MEXSection->setAttribute('Dialect''http://schemas.xmlsoap.org/ws/2004/09/mex');
        
        
$MEXRef $doc->createElementNS(self::MEXNSself::MEXPFX.':MetadataReference');
        
$MEXSection->appendChild($MEXRef);

        
$addrNode $doc->createElementNS(self::WSANSself::WSAPFX.':Address'$this->secpolicy);
        
$MEXRef->appendChild($addrNode);
        
        if (! empty(
$this->identityCert['cert'])) {
            
$idNode $doc->createElementNS(self::WSIDNSself::WSIDPFX.':Identity');
            
$endpoint->appendChild($idNode);
            
XMLSecurityDSig::staticAdd509Cert($idNode$this->identityCert['cert'],
                                   
$this->identityCert['isPem'],
                                   
$this->identityCert['isURL']);
        }
    }
}

class 
ICardSAML {
    private 
$samlDoc;
    private 
$attributes = array();
    private 
$conditions = array();
    private 
$identityCert = array('cert'=>NULL'isPem'=>True'isURL'=>False);

    private 
$samlTmpl '<saml:Assertion MajorVersion="1" MinorVersion="0" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"><saml:AttributeStatement/></saml:Assertion>';

    const 
SAML10 'urn:oasis:names:tc:SAML:1.0:assertion';
    const 
SAML10PFX 'saml';
    const 
NOTBEFORE 'NotBefore';
    const 
NOTONORAFTER 'NotOnOrAfter';
    const 
AUDIENCE 'Audience';

    public function 
__construct($issuer) {
        
$this->samlDoc = new DOMDocument();
        
$this->samlDoc->loadXML($this->samlTmpl);
        
$root $this->samlDoc->documentElement;
        
$root->setAttribute('Issuer'$issuer);
        
$root->setAttribute('IssueInstant'gmdate("Y-m-d\TH:i:s"time()).'Z');
    }
    
    public function 
addAttribute($objAttr) {
        if (! 
$objAttr instanceof ICardClaimType) {
            throw new 
Exception('Attribute must be of ICardClaimType');
        }
        if (empty(
$objAttr->name)) {
            throw new 
Exception('Attribute does not have name/value set');
        }
        
$this->attributes[] = $objAttr;
    }

    public function 
addCondition($condition$value) {
        switch (
$condition) {
            case (
self::NOTBEFORE):
            case (
self::NOTONORAFTER):
                
/* check date */
                
$xmlVal gmdate("Y-m-d\TH:i:s"$value).'Z';
                break;
            case (
self::AUDIENCE):
                
$xmlVal $value;
                break;
            default:
                throw new 
Exception('Supplied condition not supported');
        }
        
$this->conditions['condition'] = $xmlVal;
    }

    private function 
createAttributes($parent) {
        foreach (
$this->attributes AS $objAttr) {
            
$attrNode $this->samlDoc->createElementNS(self::SAML10self::SAML10PFX.':Attribute');
            
$parent->appendChild($attrNode);
            
$attrNode->setAttribute('AttributeName'$objAttr->name);
            
$attrNode->setAttribute('AttributeNamespace'$objAttr->URI);
            
$attrValue $this->samlDoc->createElementNS(self::SAML10self::SAML10PFX.':AttributeValue'$objAttr->value);
            
$attrNode->appendChild($attrValue);
        }
    }

    public function 
setX509Cert($cert$isPEMFormat=TRUE$isURL=False) {
        
$this->identityCert['cert'] = $cert;
        
$this->identityCert['isPem'] = $isPEMFormat;
        
$this->identityCert['isURL'] = $isURL;
    }

    public function 
getXMLDisplay() {
        
$doc = new DOMDocument();
        
$doc->loadXML('<wsi:DisplayToken xmlns:wsi="http://schemas.xmlsoap.org/ws/2005/05/identity"/>');
        
$token $doc->documentElement;
        foreach (
$this->attributes AS $objAttr) {
            
$claimNode $doc->createElementNS('http://schemas.xmlsoap.org/ws/2005/05/identity''wsi:DisplayClaim');
            
$token->appendChild($claimNode);
            
$claimNode->setAttribute('Uri'$objAttr->URI);

            
$dispNode $doc->createElementNS('http://schemas.xmlsoap.org/ws/2005/05/identity''wsi:DisplayTag'$objAttr->displayName);
            
$claimNode->appendChild($dispNode);

            
$dispNode $doc->createElementNS('http://schemas.xmlsoap.org/ws/2005/05/identity''wsi:Description'$objAttr->description);
            
$claimNode->appendChild($dispNode);

            
$dispNode $doc->createElementNS('http://schemas.xmlsoap.org/ws/2005/05/identity''wsi:DisplayValue'$objAttr->value);
            
$claimNode->appendChild($dispNode);
        }
        return 
$doc->saveXML($token);
    }

    public function 
asXML($strKey) {
        if (
count($this->attributes) == 0) {
            throw new 
Exception('No attributes have been defined');
        }
        
$root $this->samlDoc->documentElement;
        
        if (
count($this->conditions) > 0) {
            
$condNode $this->samlDoc->createElementNS(self::SAML10self::SAML10PFX.':Conditions');
            
$this->samlDoc->documentElement->appendChild($condNode);
            foreach (
$this->conditions AS $condition=>$value) {
                if (
$condition != self::AUDIENCE) {
                    
$condNode->setAttribute($condition$value);
                } else {
                    
$audRest $this->samlDoc->createElementNS(self::SAML10self::SAML10PFX.':AudienceRestrictionCondition');
                    
$condNode->appendChild($audRest);
                    
$audience $this->samlDoc->createElementNS(self::SAML10self::SAML10PFX.':Audience'$value);
                    
$audRest->appendChild($audience);
                }
            }
        }
        
        
$child $root->firstChild;
        while (
$child) {
            if (
$child->localName == 'AttributeStatement') {
                
$this->createAttributes($child);
            }
            
$child $child->nextSibling;
        }

        
$objDSig = new XMLSecurityDSig();
        
$objDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
        
$arOptions = array('id_name'=>'AssertionID');
        
$objDSig->addReference($this->samlDoc->documentElementXMLSecurityDSig::SHA1, array('http://www.w3.org/2000/09/xmldsig#enveloped-signature'), $arOptions);
        
$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'private'));
        
$objKey->loadKey($strKeyFalse);
        
$objDSig->sign($objKey);
        
$objDSig->add509Cert($this->identityCert['cert'], $this->identityCert['isPem'], $this->identityCert['isURL']);
        
        
$objDSig->appendSignature($root);
        
        return 
$this->samlDoc->saveXML($root);
    }
}

class 
ICardUserCredential {
    private 
$type;
    private 
$displayHint NULL;
    private 
$userName NULL;
    
    const 
UsernamePasswordCredential 1;
    const 
KerberosV5Credential 2;
    const 
X509V3Credential 3;
    const 
SelfIssuedCredential 4;

    public function 
__construct($type$dispHint=NULL) {
        if (
$type != self::UsernamePasswordCredential) {
            throw new 
Exception('Only UsernamePassword credential currently implemented');
        }
        
$this->displayHint $dispHint;
        
$this->type $type;
    }
    
    public function 
setUserName($Username) {
        
$this->userName $Username;
    }
    
    public function 
appendToDoc($parent) {
        if (! 
$parent instanceof DOMElement) {
            throw new 
Exception("Parent must be a DOMElement");
        }
        
$doc $parent->ownerDocument;
        
$usercred $doc->createElementNS(ICard::ICARDNSICard::ICARDNS_PFX.':UserCredential');
        
$parent->appendChild($usercred);
        
        if (! empty(
$this->displayHint)) {
            
$disphint $doc->createElementNS(ICard::ICARDNSICard::ICARDNS_PFX.':DisplayCredentialHint'$this->displayHint);
            
$usercred->appendChild($disphint);
        }
        switch (
$this->type) {
            case (
self::UsernamePasswordCredential):
                
$cred $doc->createElementNS(ICard::ICARDNSICard::ICARDNS_PFX.':UsernamePasswordCredential');
                
$usercred->appendChild($cred);
                if (! empty(
$this->userName)) {
                    
$uname $doc->createElementNS(ICard::ICARDNSICard::ICARDNS_PFX.':Username'$this->userName);
                    
$cred->appendChild($uname);
                }
                break;
            default:
                break;
        }
    }
}

class 
ICardTokenService {
    public 
$endPointRef NULL;
    private 
$userCredential NULL;
    
    public function 
__construct($endPoint$userCred) {
        if (! 
$endPoint instanceof ICardEndpoint) {
            throw new 
Exception("EndPoint must be an ICardEndpoint");
        }
        if (! 
$userCred instanceof ICardUserCredential) {
            throw new 
Exception("UserCredential must be an ICardUserCredential");
        }
        
$this->endPointRef $endPoint;
        
$this->userCredential $userCred;
    }

    public function 
appendToDoc($parent) {
        if (! 
$parent instanceof DOMElement) {
            throw new 
Exception("Parent must be a DOMElement");
        }
        
$doc $parent->ownerDocument;

        
$tokensvc $doc->createElementNS(ICard::ICARDNSICard::ICARDNS_PFX.':TokenService');
        
$parent->appendChild($tokensvc);
        
$this->endPointRef->appendToDoc($tokensvc);
        
$this->userCredential->appendToDoc($tokensvc);
    }
}

class 
ICardClaimType {
    public 
$URI NULL;
    public 
$displayName NULL;
    public 
$description NULL;
    public 
$name NULL;
    public 
$value NULL;

    public function 
__construct($URI$displayName NULL$description NULL) {
        
$this->URI $URI;
        
$this->displayName $displayName;
        
$this->description $description;
    }

    public function 
setValue($name$value NULL) {
        
$this->name $name;
        
$this->value $value;
    }    
    
}

class 
ICard {
    public 
$lang 'en-us';

    public 
$CardId;
    public 
$CardVersion;
    
    public 
$CardName NULL;
    private 
$CardImage NULL;
    private 
$CardImageMime NULL;
    
    public 
$Issuer;
    
    public 
$TimeExpires;
    
    public 
$RequireAppliesTo NULL;
    
    private 
$PrivacyURI NULL;
    private 
$PrivacyVersion NULL;
// change this later    private $cardDoc = NULL;
    
public $cardDoc NULL;
    
    private 
$tokenServices = array();
    private 
$tokenTypes NULL;
    private 
$claimTypes NULL;
    
    const 
ICARDNS 'http://schemas.xmlsoap.org/ws/2005/05/identity';
    const 
ICARDNS_PFX 'wsi';

    const 
WSTRUSTNS 'http://schemas.xmlsoap.org/ws/2005/02/trust';
    const 
WSTRUSTNS_PFX 'wst';

    public function 
__construct($CardId$CardVersion$Issuer$cardLang=NULL) {
        if (! empty(
$cardLang)) {
            
$this->lang $cardLang;
        }
        
$cardDoc = new DOMDocument();
        
$root $cardDoc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':InformationCard');
        
$cardDoc->appendChild($root);
        
$root->setAttribute('xml:lang'$this->lang);
        
$iref $cardDoc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':InformationCardReference');
        
$root->appendChild($iref);
        
$id $cardDoc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':CardId'$CardId);
        
$iref->appendChild($id);
        
$id $cardDoc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':CardVersion'$CardVersion);
        
$iref->appendChild($id);
        
        
$this->Issuer $Issuer;
        
        
$this->cardDoc $cardDoc;
    }
    
    public function 
setExpireTime($expireDate) {
        if ((
$timestamp strtotime($expireDate)) === false) {
            throw new 
Exception("Invalid ExpireTime");
        }
        if (
$timestamp time()) {
            throw new 
Exception("ExpireTime cannot be in the past");
        }
        
$this->TimeExpires $timestamp;
    }
    
    private function 
buildRelyingParty() {
        if (! 
is_null($this->RequireAppliesTo)) {
            
$root $this->cardDoc->documentElement;
            
$uri $this->cardDoc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':RequireAppliesTo');
            
$root->appendChild($uri);
            if (
$this->RequireAppliesTo) {
                
$uri->setAttribute('Optional''false');
            } else {
                
$uri->setAttribute('Optional''true');
            }
        }
    }
    
    public function 
requireRelyingParty($RequireAppliesTo=NULL) {
        if (! 
is_null($RequireAppliesTo)) {
            
$this->RequireAppliesTo = (bool)$RequireAppliesTo;
        } else {
            
$this->RequireAppliesTo NULL;
        }
    } 
    
    public function 
addClaimType($objClaim) {
        if (! 
$objClaim instanceof ICardClaimType) {
            throw new 
Exception('Claim must be of ICardClaimType');
        }
        if (! 
is_array($this->claimTypes)) {
            
$this->claimTypes = array();
        }
        
$this->claimTypes[] = $objClaim;
    }
    
    public function 
setTokenTypes($arTokenTypes NULL) {
        if (
is_null($arTokenTypes)) {
            
$this->tokenTypes NULL;
            return 
True;
        }
        if (! 
is_array($arTokenTypes)) {
            throw new 
Exception('NULL or array of Token Types required for input parameter');
        }
        
$this->tokenTypes $arTokenTypes;
    }
    
    public function 
setCardImage($imgFile$imgMime NULL) {
        
$this->CardImage $imgFile;
        
$this->CardImageMime $imgMime;
    }
    
    public function 
setPrivacyNotice($URI$version=NULL) {
        
$this->PrivacyURI $URI;
        
$this->PrivacyVersion $version;
    }

    public function 
addService($objSvc) {
        if (! 
$objSvc instanceof ICardTokenService) {
            throw new 
Exception('Service must be an ICardTokenService');
        }
        
$this->tokenServices[] = $objSvc;
    }

    public function 
getCard() {
        
$doc $this->cardDoc;
        
$root $doc->documentElement;

        if (! empty(
$this->CardName)) {
            
$iref $doc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':CardName'$this->CardName);
            
$root->appendChild($iref);
        }
        
        if (! empty(
$this->CardImage)) {
            
$image file_get_contents($this->CardImage);
            
$encodedImage base64_encode($image);
            
$iref $doc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':CardImage'$encodedImage);
            
$root->appendChild($iref);
            if (! empty(
$this->CardImageMime)) {
                
$iref->setAttribute('MimeType'$this->CardImageMime);
            }
        }

        
$current $doc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':Issuer'$this->Issuer);
        
$root->appendChild($current);
        
        
$currentTime time();
        
$current $doc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':TimeIssued'gmdate("Y-m-d\TH:i:s"$currentTime).'Z');
        
$root->appendChild($current);

        if (! empty(
$this->TimeExpires)) {
            
$expires $doc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':TimeExpires'gmdate("Y-m-d\TH:i:s"$this->TimeExpires).'Z');
            
$root->appendChild($expires);
        }
        
        
$tokensvclist $doc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':TokenServiceList');
        
$root->appendChild($tokensvclist);
        
        foreach (
$this->tokenServices AS $tokensvc) {
            
$tokensvc->appendToDoc($tokensvclist);
        }
        
        if (! empty(
$this->tokenTypes)) {
            
$tokenlist $doc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':SupportedTokenTypeList');
            
$root->appendChild($tokenlist);
            foreach (
$this->tokenTypes AS $tokenType) {
                
$token $doc->createElementNS(self::WSTRUSTNSself::WSTRUSTNS_PFX.':TokenType'$tokenType);
                
$tokenlist->appendChild($token);
            }
        }

        if (
is_array($this->claimTypes)) {
            
$claimlist $doc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':SupportedClaimTypeList');
            
$root->appendChild($claimlist);
            foreach (
$this->claimTypes AS $objClaim) {
                
$claimtype $doc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':SupportedClaimType');
                
$claimlist->appendChild($claimtype);
                
$claimtype->setAttribute('Uri'$objClaim->URI);
                if (! empty(
$objClaim->displayName)) {
                    
$claimdisp $doc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':DisplayTag'$objClaim->displayName);
                    
$claimtype->appendChild($claimdisp);
                }
                if (! empty(
$objClaim->description)) {
                    
$claimdesc $doc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':Description'$objClaim->description);
                    
$claimtype->appendChild($claimdesc);
                }
            }
        }

        
$this->buildRelyingParty();
        if (! empty(
$this->PrivacyURI)) {
            
$uri $doc->createElementNS(self::ICARDNSself::ICARDNS_PFX.':PrivacyNotice'$this->PrivacyURI);
            
$root->appendChild($uri);
            if (! 
is_null($this->PrivacyVersion)) {
                
$uri->setAttribute('Version'$this->PrivacyVersion);
            }
        }
        
        
$doc->formatOutput True;
        return 
$doc->saveXML();
    }
}
?>