You are here

SecurityAdvisory.php in Drupal 9

File

core/modules/system/src/SecurityAdvisories/SecurityAdvisory.php
View source
<?php

namespace Drupal\system\SecurityAdvisories;

use Symfony\Component\Validator\Constraints\Choice;
use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Type;
use Symfony\Component\Validator\Validation;

/**
 * Provides a security advisory value object.
 *
 * These come from the security advisory feed on Drupal.org.
 *
 * @internal
 *
 * @see https://www.drupal.org/docs/updating-drupal/responding-to-critical-security-update-advisories#s-drupalorg-json-advisories-feed
 */
final class SecurityAdvisory {

  /**
   * The title of the advisory.
   *
   * @var string
   */
  protected $title;

  /**
   * The project name for the advisory.
   *
   * @var string
   */
  protected $project;

  /**
   * The project type for the advisory.
   *
   * @var string
   */
  protected $type;

  /**
   * Whether this advisory is a PSA instead of another type of advisory.
   *
   * @var bool
   */
  protected $isPsa;

  /**
   * The currently insecure versions of the project.
   *
   * @var string[]
   */
  protected $insecureVersions;

  /**
   * The URL to the advisory.
   *
   * @var string
   */
  protected $url;

  /**
   * Constructs a SecurityAdvisories object.
   *
   * @param string $title
   *   The title of the advisory.
   * @param string $project
   *   The project name.
   * @param string $type
   *   The project type.
   * @param bool $is_psa
   *   Whether this advisory is a public service announcement.
   * @param string $url
   *   The URL to the advisory.
   * @param string[] $insecure_versions
   *   The versions of the project that are currently insecure. For public
   *   service announcements this list does not include versions that will be
   *   marked as insecure when the new security release is published.
   */
  private function __construct(string $title, string $project, string $type, bool $is_psa, string $url, array $insecure_versions) {
    $this->title = $title;
    $this->project = $project;
    $this->type = $type;
    $this->isPsa = $is_psa;
    $this->url = $url;
    $this->insecureVersions = $insecure_versions;
  }

  /**
   * Creates a SecurityAdvisories instance from an array.
   *
   * @param mixed[] $data
   *   The security advisory data as returned from the JSON feed.
   *
   * @return self
   *   A new SecurityAdvisories object.
   */
  public static function createFromArray(array $data) : self {
    static::validateAdvisoryData($data);
    return new static($data['title'], $data['project'], $data['type'], $data['is_psa'], $data['link'], $data['insecure']);
  }

  /**
   * Validates the security advisory data.
   *
   * @param mixed[] $data
   *   The advisory data.
   *
   * @throws \UnexpectedValueException
   *   Thrown if security advisory data is not valid.
   */
  protected static function validateAdvisoryData(array $data) : void {
    $not_blank_constraints = [
      new Type([
        'type' => 'string',
      ]),
      new NotBlank(),
    ];
    $collection_constraint = new Collection([
      'fields' => [
        'title' => $not_blank_constraints,
        'project' => $not_blank_constraints,
        'type' => $not_blank_constraints,
        'link' => $not_blank_constraints,
        'is_psa' => new Choice([
          'choices' => [
            1,
            '1',
            0,
            '0',
            TRUE,
            FALSE,
          ],
        ]),
        'insecure' => new Type([
          'type' => 'array',
        ]),
      ],
      // Allow unknown fields, in the case that new fields are added to JSON
      // feed validation should still pass.
      'allowExtraFields' => TRUE,
    ]);
    $violations = Validation::createValidator()
      ->validate($data, $collection_constraint);
    if ($violations
      ->count()) {
      foreach ($violations as $violation) {
        $violation_messages[] = "Field " . $violation
          ->getPropertyPath() . ": " . $violation
          ->getMessage();
      }
      throw new \UnexpectedValueException('Malformed security advisory: ' . implode(",\n", $violation_messages));
    }
  }

  /**
   * Gets the title.
   *
   * @return string
   *   The project title.
   */
  public function getTitle() : string {
    return $this->title;
  }

  /**
   * Gets the project associated with the advisory.
   *
   * @return string
   *   The project name.
   */
  public function getProject() : string {
    return $this->project;
  }

  /**
   * Gets the type of project associated with the advisory.
   *
   * @return string
   *   The project type.
   */
  public function getProjectType() : string {
    return $this->type;
  }

  /**
   * Whether the security advisory is for core or not.
   *
   * @return bool
   *   TRUE if the advisory is for core, or FALSE otherwise.
   */
  public function isCoreAdvisory() : bool {
    return $this
      ->getProjectType() === 'core';
  }

  /**
   * Whether the security advisory is a public service announcement or not.
   *
   * @return bool
   *   TRUE if the advisory is a public service announcement, or FALSE
   *   otherwise.
   */
  public function isPsa() : bool {
    return $this->isPsa;
  }

  /**
   * Gets the currently insecure versions of the project.
   *
   * @return string[]
   *   The versions of the project that are currently insecure.
   */
  public function getInsecureVersions() : array {
    return $this->insecureVersions;
  }

  /**
   * Gets the URL to the security advisory.
   *
   * @return string
   *   The URL to the security advisory.
   */
  public function getUrl() : string {
    return $this->url;
  }

}

Classes

Namesort descending Description
SecurityAdvisory Provides a security advisory value object.