<?php

namespace Drupal\miniorange_saml;

/**
 * Class for reading and parsing SAML metadata.
 */
class MetadataReader {

  /**
   * List of identity providers.
   *
   * @var array
   */
  private $identityProviders;

  /**
   * List of service providers.
   *
   * @var array
   */
  private $serviceProviders;

  /**
   * MetadataReader constructor.
   *
   * @param \DOMNode|null $xml
   *   XML data to parse.
   */
  public function __construct(?\DOMNode $xml = NULL) {
    $this->identityProviders = [];
    $this->serviceProviders = [];
    $entityDescriptors = Utilities::xpQuery($xml, './saml_metadata:EntityDescriptor');
    foreach ($entityDescriptors as $entityDescriptor) {
      $idpSSODescriptor = Utilities::xpQuery($entityDescriptor, './saml_metadata:IDPSSODescriptor');

      if (isset($idpSSODescriptor) && !empty($idpSSODescriptor)) {
        array_push($this->identityProviders, new IdentityProviders($entityDescriptor));
      }
      // @todo add sp descriptor
    }
  }

  /**
   * Get the list of identity providers.
   *
   * @return array
   *   List of identity providers.
   */
  public function getIdentityProviders() {
    return $this->identityProviders;
  }

  /**
   * Get the list of service providers.
   *
   * @return array
   *   List of service providers.
   */
  public function getServiceProviders() {
    return $this->serviceProviders;
  }

}


/**
 * Class representing an identity provider.
 */
class IdentityProviders {

  /**
   * Identity provider name.
   *
   * @var string
   */
  private $idpName;

  /**
   * Entity ID.
   *
   * @var string
   */
  private $entityID;

  /**
   * Login details.
   *
   * @var array
   */
  private $loginDetails;

  /**
   * Logout details.
   *
   * @var array
   */
  private $logoutDetails;

  /**
   * Signing certificates.
   *
   * @var array
   */
  private $signingCertificate;

  /**
   * Encryption certificates.
   *
   * @var array
   */
  private $encryptionCertificate;

  /**
   * Whether the request is signed.
   *
   * @var bool
   */
  private $signedRequest;

  /**
   * IdentityProviders constructor.
   *
   * @param \DOMElement|null $xml
   *   XML data to parse.
   */
  public function __construct(?\DOMElement $xml = NULL) {
    $this->idpName = '';
    $this->loginDetails = [];
    $this->logoutDetails = [];
    $this->signingCertificate = [];
    $this->encryptionCertificate = [];
    if ($xml->hasAttribute('entityID')) {
      $this->entityID = $xml->getAttribute('entityID');
    }

    if ($xml->hasAttribute('WantAuthnRequestsSigned')) {
      $this->signedRequest = $xml->getAttribute('WantAuthnRequestsSigned');
    }

    $idpSSODescriptor = Utilities::xpQuery($xml, './saml_metadata:IDPSSODescriptor');
    if (count($idpSSODescriptor) > 1) {
      throw new \Exception('More than one <IDPSSODescriptor> in <EntityDescriptor>.');
    }
    elseif (empty($idpSSODescriptor)) {
      throw new \Exception('Missing required <IDPSSODescriptor> in <EntityDescriptor>.');
    }
    $idpSSODescriptorEL = $idpSSODescriptor[0];
    $info = Utilities::xpQuery($xml, './saml_metadata:Extensions');
    if ($info) {
      $this->parseInfo($idpSSODescriptorEL);
    }
    $this->parseSsoService($idpSSODescriptorEL);
    $this->parseSloService($idpSSODescriptorEL);
    $this->parsex509Certificate($idpSSODescriptorEL);
  }

  /**
   * Parse additional information from the XML.
   *
   * @param \DOMElement $xml
   *   XML node to parse.
   */
  private function parseInfo($xml) {
    $displayNames = Utilities::xpQuery($xml, './mdui:UIInfo/mdui:DisplayName');
    foreach ($displayNames as $name) {
      if ($name->hasAttribute('xml:lang') && $name->getAttribute('xml:lang') == "en") {
        $this->idpName = $name->textContent;
      }
    }
  }

  /**
   * Parse SSO service details from the XML.
   *
   * @param \DOMElement $xml
   *   XML node to parse.
   */
  private function parseSsoService($xml) {
    $ssoServices = Utilities::xpQuery($xml, './saml_metadata:SingleSignOnService');
    foreach ($ssoServices as $ssoService) {
      $binding = str_replace("urn:oasis:names:tc:SAML:2.0:bindings:", "", $ssoService->getAttribute('Binding'));
      $this->loginDetails = array_merge($this->loginDetails, [$binding => $ssoService->getAttribute('Location')]);
    }
  }

  /**
   * Parse SLO service details from the XML.
   *
   * @param \DOMElement $xml
   *   XML node to parse.
   */
  private function parseSloService($xml) {
    $sloServices = Utilities::xpQuery($xml, './saml_metadata:SingleLogoutService');
    foreach ($sloServices as $sloService) {
      $binding = str_replace("urn:oasis:names:tc:SAML:2.0:bindings:", "", $sloService->getAttribute('Binding'));
      $this->logoutDetails = array_merge($this->logoutDetails, [$binding => $sloService->getAttribute('Location')]);
    }
  }

  /**
   * Parse certificates from the XML.
   *
   * @param \DOMElement $xml
   *   XML node to parse.
   */
  private function parsex509Certificate($xml) {
    foreach (Utilities::xpQuery($xml, './saml_metadata:KeyDescriptor') as $keyDescriptorNode) {
      if ($keyDescriptorNode->hasAttribute('use')) {
        if ($keyDescriptorNode->getAttribute('use') == 'encryption') {
          $this->parseEncryptionCertificate($keyDescriptorNode);
        }
        else {
          $this->parseSigningCertificate($keyDescriptorNode);
        }
      }
      else {
        $this->parseSigningCertificate($keyDescriptorNode);
      }
    }
  }

  /**
   * Parse the signing certificate from the XML.
   *
   * @param \DOMElement $xml
   *   XML node to parse.
   */
  private function parseSigningCertificate($xml) {
    $certNode = Utilities::xpQuery($xml, './ds:KeyInfo/ds:X509Data/ds:X509Certificate');
    $certData = trim($certNode[0]->textContent);
    $certData = str_replace(["\r", "\n", "\t", ' '], '', $certData);
    if (!empty($certNode)) {
      array_push($this->signingCertificate, Utilities::sanitizeCertificate($certData));
    }
  }

  /**
   * Parse the encryption certificate from the XML.
   *
   * @param \DOMElement $xml
   *   XML node to parse.
   */
  private function parseEncryptionCertificate($xml) {
    $certNode = Utilities::xpQuery($xml, './ds:KeyInfo/ds:X509Data/ds:X509Certificate');
    $certData = trim($certNode[0]->textContent);
    $certData = str_replace(["\r", "\n", "\t", ' '], '', $certData);
    if (!empty($certNode)) {
      array_push($this->encryptionCertificate, $certData);
    }
  }

  /**
   * Get the identity provider name.
   *
   * @return string
   *   Identity provider name.
   */
  public function getIdpName() {
    return "";
  }

  /**
   * Get the entity ID.
   *
   * @return string
   *   Entity ID.
   */
  public function getEntityId() {
    return $this->entityID;
  }

  /**
   * Get the login URL for a specific binding.
   *
   * @param string $binding
   *   The binding type.
   *
   * @return string
   *   Login URL.
   */
  public function getLoginUrl($binding) {
    return $this->loginDetails[$binding];
  }

  /**
   * Get the logout URL for a specific binding.
   *
   * @param string $binding
   *   The binding type.
   *
   * @return string
   *   Logout URL.
   */
  public function getLogoutUrl($binding) {
    return $this->logoutDetails[$binding];
  }

  /**
   * Get the login details.
   *
   * @return array
   *   Login details.
   */
  public function getLoginDetails() {
    return $this->loginDetails;
  }

  /**
   * Get the logout details.
   *
   * @return array
   *   Logout details.
   */
  public function getLogoutDetails() {
    return $this->logoutDetails;
  }

  /**
   * Get the signing certificates.
   *
   * @return array
   *   Signing certificates.
   */
  public function getSigningCertificate() {
    return $this->signingCertificate;
  }

  /**
   * Get the encryption certificate.
   *
   * @return string
   *   Encryption certificate.
   */
  public function getEncryptionCertificate() {
    return $this->encryptionCertificate[0];
  }

  /**
   * Check if the request is signed.
   *
   * @return bool
   *   Whether the request is signed.
   */
  public function isRequestSigned() {
    return $this->signedRequest;
  }

}

/**
 * Class for service providers.
 */
class ServiceProviders {
  // @todo .
}
