Full Code of fr05t1k/esia for AI

master c1750575b7eb cached
42 files
73.0 KB
20.6k tokens
140 symbols
1 requests
Download .txt
Repository: fr05t1k/esia
Branch: master
Commit: c1750575b7eb
Files: 42
Total size: 73.0 KB

Directory structure:
gitextract_ro8_8jf5/

├── .gitignore
├── .travis.yml
├── README.md
├── _config.yml
├── codeception.yml
├── composer.json
├── src/
│   └── Esia/
│       ├── Config.php
│       ├── Exceptions/
│       │   ├── AbstractEsiaException.php
│       │   ├── ForbiddenException.php
│       │   ├── InvalidConfigurationException.php
│       │   └── RequestFailException.php
│       ├── Http/
│       │   ├── Exceptions/
│       │   │   └── HttpException.php
│       │   └── GuzzleHttpClient.php
│       ├── OpenId.php
│       └── Signer/
│           ├── AbstractSignerPKCS7.php
│           ├── CliSignerPKCS7.php
│           ├── Exceptions/
│           │   ├── CannotGenerateRandomIntException.php
│           │   ├── CannotReadCertificateException.php
│           │   ├── CannotReadPrivateKeyException.php
│           │   ├── NoSuchCertificateFileException.php
│           │   ├── NoSuchKeyFileException.php
│           │   ├── NoSuchTmpDirException.php
│           │   └── SignFailException.php
│           ├── SignerInterface.php
│           └── SignerPKCS7.php
└── tests/
    ├── .configure-gost-openssl.sh
    ├── _bootstrap.php
    ├── _data/
    │   ├── server-gost.crt
    │   ├── server-gost.key
    │   ├── server.crt
    │   ├── server.csr
    │   └── server.key
    ├── _support/
    │   ├── Helper/
    │   │   └── Unit.php
    │   ├── UnitTester.php
    │   └── _generated/
    │       └── UnitTesterActions.php
    ├── unit/
    │   ├── ConfigTest.php
    │   ├── Http/
    │   │   └── GuzzleHttpClientTest.php
    │   ├── OpenIdCliOpensslTest.php
    │   ├── OpenIdTest.php
    │   ├── Signer/
    │   │   └── SignerPKCS7Test.php
    │   └── _bootstrap.php
    └── unit.suite.yml

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.idea
tests/tmp/*
tests/_output/*
public/*
vendor

tests/_data/non_readable_file

================================================
FILE: .travis.yml
================================================
dist: bionic
language: php
addons:
  apt:
    packages:
    - libengine-gost-openssl1.1

before_install:
    - sudo bash tests/.configure-gost-openssl.sh

php:
  - 7.1
  - 7.2
  - 7.3
  - 7.4
  - 8.0

install:
  - travis_retry composer self-update
  - travis_retry composer --version
  - travis_retry composer update --prefer-dist --no-interaction

script:
  - chmod 000 tests/_data/non_readable_file
  - php vendor/codeception/codeception/codecept run


================================================
FILE: README.md
================================================

# Единая система идентификации и аутентификации (ЕСИА) OpenId 

[![Build Status](https://travis-ci.org/fr05t1k/esia.svg?branch=master)](https://travis-ci.org/fr05t1k/esia)

# Описание
Компонент для авторизации на портале "Госуслуги".

# Внимание!
Получив токен вы можете выполнять любые API запросы. Библиотека не поддерживает все существующие методы в API, а предоставляет только самые базовые. Основная цель библиотеки - получение токена.

# Установка

При помощи [composer](https://getcomposer.org/download/):
```
composer require --prefer-dist fr05t1k/esia
```
Или добавьте в composer.json

```
"fr05t1k/esia" : "^2.0"
```

# Как использовать 

Пример получения ссылки для авторизации
```php
<?php 
$config = new \Esia\Config([
  'clientId' => 'INSP03211',
  'redirectUrl' => 'http://my-site.com/response.php',
  'portalUrl' => 'https://esia-portal1.test.gosuslugi.ru/',
  'scope' => ['fullname', 'birthdate'],
]);
$esia = new \Esia\OpenId($config);
$esia->setSigner(new \Esia\Signer\SignerPKCS7(
    'my-site.com.pem',
    'my-site.com.pem',
    'password',
    '/tmp'
));
?>

<a href="<?=$esia->buildUrl()?>">Войти через портал госуслуги</a>
```

После редиректа на ваш `redirectUrl` вы получите в `$_GET['code']` код для получения токена

Пример получения токена и информации о пользователе

```php

$esia = new \Esia\OpenId($config);

// Вы можете использовать токен в дальнейшем вместе с oid 
$token = $esia->getToken($_GET['code']);

$personInfo = $esia->getPersonInfo();
$addressInfo = $esia->getAddressInfo();
$contactInfo = $esia->getContactInfo();
$documentInfo = $esia->getDocInfo();

```
# Конфиг

`clientId` - ID вашего приложения.

`redirectUrl` - URL куда будет перенаправлен ответ с кодом.

`portalUrl` - по умолчанию: `https://esia-portal1.test.gosuslugi.ru/`. Домен портала для авторизация (только домен).

`codeUrlPath` - по умолчанию: `aas/oauth2/ac`. URL для получения кода.

`tokenUrlPath` - по умолчанию: `aas/oauth2/te`. URL для получение токена.

`scope` - по умолчанию: `fullname birthdate gender email mobile id_doc snils inn`. Запрашиваемые права у пользователя.

`privateKeyPath` - путь до приватного ключа.

`privateKeyPassword` - пароль от приватного ключа.

`certPath` - путь до сертификата.

`tmpPath` - путь до дериктории где будет проходить подпись (должна быть доступна для записи).

# Токен и oid

Токен - jwt токен которые вы получаете от ЕСИА для дальнейшего взаимодействия

oid - уникальный идентификатор владельца токена

## Как получить oid?
Если 2 способа:
1. oid содержится в jwt токене, расшифровав его
2. После получения токена oid сохраняется в config и получить можно так 
```php
$esia->getConfig()->getOid();
```

## Переиспользование Токена

Дополнительно укажите токен и идентификатор в конфиге
```php
$config->setToken($jwt);
$config->setOid($oid);
```


================================================
FILE: _config.yml
================================================
theme: jekyll-theme-cayman

================================================
FILE: codeception.yml
================================================
actor: Tester
paths:
    tests: tests
    log: tests/_output
    data: tests/_data
    support: tests/_support
    envs: tests/_envs
bootstrap: _bootstrap.php
settings:
    colors: true
    memory_limit: 1024M
extensions:
    enabled:
        - Codeception\Extension\RunFailed
coverage:
    enabled: true
    remote: false
    whitelist:
            include:
                - src/*



================================================
FILE: composer.json
================================================
{
  "name": "fr05t1k/esia",
  "license": "MIT",
  "description": "OpenID ESIA authenticating",
  "keywords": [
    "esia",
    "openid",
    "egov"
  ],
  "autoload": {
    "psr-4": {
      "Esia\\": "src/Esia"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "tests\\" : "tests"
    }
  },
  "require": {
    "php": "^7.1|^8.0",
    "guzzlehttp/guzzle": "^6.1.0|^7.0",
    "psr/log": "^1.0",
    "psr/http-message": "^1.0",
    "psr/http-client": "^1.0"
  },
  "suggest": {
    "ext-openssl": "SignerPKCS7 support"
  },
  "require-dev": {
    "roave/security-advisories": "dev-latest",
    "codeception/codeception": "^4.0",
    "codeception/module-asserts": "^1.3"
  }
}


================================================
FILE: src/Esia/Config.php
================================================
<?php

namespace Esia;

use Esia\Exceptions\InvalidConfigurationException;

class Config
{
    private $clientId;
    private $redirectUrl;
    private $privateKeyPath;
    private $certPath;

    private $portalUrl = 'http://esia-portal1.test.gosuslugi.ru/';
    private $tokenUrlPath = 'aas/oauth2/te';
    private $codeUrlPath = 'aas/oauth2/ac';
    private $personUrlPath = 'rs/prns';
    private $logoutUrlPath = 'idp/ext/Logout';
    private $privateKeyPassword = '';

    private $scope = [
        'fullname',
        'birthdate',
        'gender',
        'email',
        'mobile',
        'id_doc',
        'snils',
        'inn',
    ];

    private $tmpPath = '/var/tmp';

    private $responseType = 'code';
    private $accessType = 'offline';

    private $token = '';
    private $oid = '';

    /**
     * Config constructor.
     *
     * @throws InvalidConfigurationException
     */
    public function __construct(array $config = [])
    {
        // Required params
        $this->clientId = $config['clientId'] ?? $this->clientId;
        if (!$this->clientId) {
            throw new InvalidConfigurationException('Please provide clientId');
        }

        $this->redirectUrl = $config['redirectUrl'] ?? $this->redirectUrl;
        if (!$this->redirectUrl) {
            throw new InvalidConfigurationException('Please provide redirectUrl');
        }

        $this->privateKeyPath = $config['privateKeyPath'] ?? $this->privateKeyPath;
        if (!$this->privateKeyPath) {
            throw new InvalidConfigurationException('Please provide privateKeyPath');
        }
        $this->certPath = $config['certPath'] ?? $this->certPath;
        if (!$this->certPath) {
            throw new InvalidConfigurationException('Please provide certPath');
        }

        $this->portalUrl = $config['portalUrl'] ?? $this->portalUrl;
        $this->tokenUrlPath = $config['tokenUrlPath'] ?? $this->tokenUrlPath;
        $this->codeUrlPath = $config['codeUrlPath'] ?? $this->codeUrlPath;
        $this->personUrlPath = $config['personUrlPath'] ?? $this->personUrlPath;
        $this->logoutUrlPath = $config['logoutUrlPath'] ?? $this->logoutUrlPath;
        $this->privateKeyPassword = $config['privateKeyPassword'] ?? $this->privateKeyPassword;
        $this->oid = $config['oid'] ?? $this->oid;
        $this->scope = $config['scope'] ?? $this->scope;
        if (!is_array($this->scope)) {
            throw new InvalidConfigurationException('scope must be array of strings');
        }

        $this->responseType = $config['responseType'] ?? $this->responseType;
        $this->accessType = $config['accessType'] ?? $this->accessType;
        $this->tmpPath = $config['tmpPath'] ?? $this->tmpPath;
        $this->token = $config['token'] ?? $this->token;
    }

    public function getPortalUrl(): string
    {
        return $this->portalUrl;
    }

    public function getPrivateKeyPath(): string
    {
        return $this->privateKeyPath;
    }

    public function getPrivateKeyPassword(): string
    {
        return $this->privateKeyPassword;
    }

    public function getCertPath(): string
    {
        return $this->certPath;
    }
    
    public function getOid(): string
    {
        return $this->oid;
    }

    public function setOid(string $oid): void
    {
        $this->oid = $oid;
    }

    public function getScope(): array
    {
        return $this->scope;
    }

    public function getScopeString(): string
    {
        return implode(' ', $this->scope);
    }

    public function getResponseType(): string
    {
        return $this->responseType;
    }

    public function getAccessType(): string
    {
        return $this->accessType;
    }

    public function getTmpPath(): string
    {
        return $this->tmpPath;
    }

    public function getToken(): ?string
    {
        return $this->token;
    }

    public function setToken(string $token): void
    {
        $this->token = $token;
    }

    public function getClientId(): string
    {
        return $this->clientId;
    }

    public function getRedirectUrl(): string
    {
        return $this->redirectUrl;
    }

    /**
     * Return an url for request to get an access token
     */
    public function getTokenUrl(): string
    {
        return $this->portalUrl . $this->tokenUrlPath;
    }

    /**
     * Return an url for request to get an authorization code
     */
    public function getCodeUrl(): string
    {
        return $this->portalUrl . $this->codeUrlPath;
    }

    /**
     * @return string
     * @throws InvalidConfigurationException
     */
    public function getPersonUrl(): string
    {
        if (!$this->oid) {
            throw new InvalidConfigurationException('Please provide oid');
        }
        return $this->portalUrl . $this->personUrlPath . '/' . $this->oid;
    }

    /**
     * Return an url for logout
     */
    public function getLogoutUrl(): string
    {
        return $this->portalUrl . $this->logoutUrlPath;
    }
}


================================================
FILE: src/Esia/Exceptions/AbstractEsiaException.php
================================================
<?php

namespace Esia\Exceptions;

use Exception;

abstract class AbstractEsiaException extends Exception
{
}


================================================
FILE: src/Esia/Exceptions/ForbiddenException.php
================================================
<?php

namespace Esia\Exceptions;

class ForbiddenException extends AbstractEsiaException
{
    protected function getMessageForCode(int $code): string
    {
        return 'Forbidden';
    }
}


================================================
FILE: src/Esia/Exceptions/InvalidConfigurationException.php
================================================
<?php

namespace Esia\Exceptions;

class InvalidConfigurationException extends AbstractEsiaException
{
}


================================================
FILE: src/Esia/Exceptions/RequestFailException.php
================================================
<?php

namespace Esia\Exceptions;

class RequestFailException extends AbstractEsiaException
{
}


================================================
FILE: src/Esia/Http/Exceptions/HttpException.php
================================================
<?php

namespace Esia\Http\Exceptions;

use Psr\Http\Client\ClientExceptionInterface;
use RuntimeException;

class HttpException extends RuntimeException implements ClientExceptionInterface
{
}


================================================
FILE: src/Esia/Http/GuzzleHttpClient.php
================================================
<?php

namespace Esia\Http;

use Esia\Http\Exceptions\HttpException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

class GuzzleHttpClient implements ClientInterface
{
    /**
     * @var Client
     */
    private $guzzle;

    /**
     * GuzzleHttpClient constructor.
     */
    public function __construct(Client $guzzle)
    {
        $this->guzzle = $guzzle;
    }

    /**
     * Sends a PSR-7 request and returns a PSR-7 response.
     *
     * Every technically correct HTTP response MUST be returned as is, even if it represents a HTTP
     * error response or a redirect instruction. The only special case is 1xx responses, which MUST
     * be assembled in the HTTP client.
     *
     * The client MAY do modifications to the Request before sending it. Because PSR-7 objects are
     * immutable, one cannot assume that the object passed to ClientInterface::sendRequest() will be the same
     * object that is actually sent. For example the Request object that is returned by an exception MAY
     * be a different object than the one passed to sendRequest, so comparison by reference (===) is not possible.
     *
     * {@link https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message-meta.md#why-value-objects}
     *
     * @param RequestInterface $request
     *
     * @return ResponseInterface
     *
     * @throws ClientExceptionInterface If an error happens during processing the request.
     */
    public function sendRequest(RequestInterface $request): ResponseInterface
    {
        try {
            return $this->guzzle->send($request);
        } catch (GuzzleException $e) {
            throw new HttpException($e->getMessage(), $e->getCode(), $e);
        }
    }
}


================================================
FILE: src/Esia/OpenId.php
================================================
<?php

namespace Esia;

use Esia\Exceptions\AbstractEsiaException;
use Esia\Exceptions\ForbiddenException;
use Esia\Exceptions\RequestFailException;
use Esia\Http\GuzzleHttpClient;
use Esia\Signer\Exceptions\CannotGenerateRandomIntException;
use Esia\Signer\Exceptions\SignFailException;
use Esia\Signer\SignerInterface;
use Esia\Signer\SignerPKCS7;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Psr7\Request;
use InvalidArgumentException;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;
use RuntimeException;

/**
 * Class OpenId
 */
class OpenId
{
    use LoggerAwareTrait;

    /**
     * @var SignerInterface
     */
    private $signer;

    /**
     * Http Client
     *
     * @var ClientInterface
     */
    private $client;

    /**
     * Config
     *
     * @var Config
     */
    private $config;

    public function __construct(Config $config, ClientInterface $client = null)
    {
        $this->config = $config;
        $this->client = $client ?? new GuzzleHttpClient(new Client());
        $this->logger = new NullLogger();
        $this->signer = new SignerPKCS7(
            $config->getCertPath(),
            $config->getPrivateKeyPath(),
            $config->getPrivateKeyPassword(),
            $config->getTmpPath()
        );
    }

    /**
     * Replace default signer
     */
    public function setSigner(SignerInterface $signer): void
    {
        $this->signer = $signer;
    }

    /**
     * Get config
     */
    public function getConfig(): Config
    {
        return $this->config;
    }

    /**
     * Return an url for authentication
     *
     * ```php
     *     <a href="<?=$esia->buildUrl()?>">Login</a>
     * ```
     *
     * @return string|false
     * @throws SignFailException
     */
    public function buildUrl()
    {
        $timestamp = $this->getTimeStamp();
        $state = $this->buildState();
        $message = $this->config->getScopeString()
            . $timestamp
            . $this->config->getClientId()
            . $state;

        $clientSecret = $this->signer->sign($message);

        $url = $this->config->getCodeUrl() . '?%s';

        $params = [
            'client_id' => $this->config->getClientId(),
            'client_secret' => $clientSecret,
            'redirect_uri' => $this->config->getRedirectUrl(),
            'scope' => $this->config->getScopeString(),
            'response_type' => $this->config->getResponseType(),
            'state' => $state,
            'access_type' => $this->config->getAccessType(),
            'timestamp' => $timestamp,
        ];

        $request = http_build_query($params);

        return sprintf($url, $request);
    }

    /**
     * Return an url for logout
     */
    public function buildLogoutUrl(string $redirectUrl = null): string
    {
        $url = $this->config->getLogoutUrl() . '?%s';
        $params = [
            'client_id' => $this->config->getClientId(),
        ];

        if ($redirectUrl) {
            $params['redirect_url'] = $redirectUrl;
        }

        $request = http_build_query($params);

        return sprintf($url, $request);
    }

    /**
     * Method collect a token with given code
     *
     * @throws SignFailException
     * @throws AbstractEsiaException
     */
    public function getToken(string $code): string
    {
        $timestamp = $this->getTimeStamp();
        $state = $this->buildState();

        $clientSecret = $this->signer->sign(
            $this->config->getScopeString()
            . $timestamp
            . $this->config->getClientId()
            . $state
        );

        $body = [
            'client_id' => $this->config->getClientId(),
            'code' => $code,
            'grant_type' => 'authorization_code',
            'client_secret' => $clientSecret,
            'state' => $state,
            'redirect_uri' => $this->config->getRedirectUrl(),
            'scope' => $this->config->getScopeString(),
            'timestamp' => $timestamp,
            'token_type' => 'Bearer',
            'refresh_token' => $state,
        ];

        $payload = $this->sendRequest(
            new Request(
                'POST',
                $this->config->getTokenUrl(),
                [
                    'Content-Type' => 'application/x-www-form-urlencoded',
                ],
                http_build_query($body)
            )
        );

        $this->logger->debug('Payload: ', $payload);

        $token = $payload['access_token'];
        $this->config->setToken($token);

        # get object id from token
        $chunks = explode('.', $token);
        $payload = json_decode($this->base64UrlSafeDecode($chunks[1]), true);
        $this->config->setOid($payload['urn:esia:sbj_id']);

        return $token;
    }

    /**
     * Fetch person info from current person
     *
     * You must collect token person before
     * calling this method
     *
     * @throws AbstractEsiaException
     */
    public function getPersonInfo(): array
    {
        $url = $this->config->getPersonUrl();

        return $this->sendRequest(new Request('GET', $url));
    }

    /**
     * Fetch contact info about current person
     *
     * You must collect token person before
     * calling this method
     *
     * @throws Exceptions\InvalidConfigurationException
     * @throws AbstractEsiaException
     */
    public function getContactInfo(): array
    {
        $url = $this->config->getPersonUrl() . '/ctts';
        $payload = $this->sendRequest(new Request('GET', $url));

        if ($payload && $payload['size'] > 0) {
            return $this->collectArrayElements($payload['elements']);
        }

        return $payload;
    }


    /**
     * Fetch address from current person
     *
     * You must collect token person before
     * calling this method
     *
     * @throws Exceptions\InvalidConfigurationException
     * @throws AbstractEsiaException
     */
    public function getAddressInfo(): array
    {
        $url = $this->config->getPersonUrl() . '/addrs';
        $payload = $this->sendRequest(new Request('GET', $url));

        if ($payload['size'] > 0) {
            return $this->collectArrayElements($payload['elements']);
        }

        return $payload;
    }

    /**
     * Fetch documents info about current person
     *
     * You must collect token person before
     * calling this method
     *
     * @throws Exceptions\InvalidConfigurationException
     * @throws AbstractEsiaException
     */
    public function getDocInfo(): array
    {
        $url = $this->config->getPersonUrl() . '/docs';

        $payload = $this->sendRequest(new Request('GET', $url));

        if ($payload && $payload['size'] > 0) {
            return $this->collectArrayElements($payload['elements']);
        }

        return $payload;
    }

    /**
     * This method can iterate on each element
     * and fetch entities from esia by url
     *
     * @throws AbstractEsiaException
     */
    private function collectArrayElements($elements): array
    {
        $result = [];
        foreach ($elements as $elementUrl) {
            $elementPayload = $this->sendRequest(new Request('GET', $elementUrl));

            if ($elementPayload) {
                $result[] = $elementPayload;
            }
        }

        return $result;
    }

    /**
     * @throws AbstractEsiaException
     */
    private function sendRequest(RequestInterface $request): array
    {
        try {
            if ($this->config->getToken()) {
                /** @noinspection CallableParameterUseCaseInTypeContextInspection */
                $request = $request->withHeader('Authorization', 'Bearer ' . $this->config->getToken());
            }
            $response = $this->client->sendRequest($request);
            $responseBody = json_decode($response->getBody()->getContents(), true);

            if (!is_array($responseBody)) {
                throw new RuntimeException(
                    sprintf(
                        'Cannot decode response body. JSON error (%d): %s',
                        json_last_error(),
                        json_last_error_msg()
                    )
                );
            }

            return $responseBody;
        } catch (ClientExceptionInterface $e) {
            $this->logger->error('Request was failed', ['exception' => $e]);
            $prev = $e->getPrevious();

            // Only for Guzzle
            if ($prev instanceof BadResponseException
                && $prev->getResponse() !== null
                && $prev->getResponse()->getStatusCode() === 403
            ) {
                throw new ForbiddenException('Request is forbidden', 0, $e);
            }

            throw new RequestFailException('Request is failed', 0, $e);
        } catch (RuntimeException $e) {
            $this->logger->error('Cannot read body', ['exception' => $e]);
            throw new RequestFailException('Cannot read body', 0, $e);
        } catch (InvalidArgumentException $e) {
            $this->logger->error('Wrong header', ['exception' => $e]);
            throw new RequestFailException('Wrong header', 0, $e);
        }
    }

    private function getTimeStamp(): string
    {
        return date('Y.m.d H:i:s O');
    }

    /**
     * Generate state with uuid
     *
     * @throws SignFailException
     */
    private function buildState(): string
    {
        try {
            return sprintf(
                '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
                random_int(0, 0xffff),
                random_int(0, 0xffff),
                random_int(0, 0xffff),
                random_int(0, 0x0fff) | 0x4000,
                random_int(0, 0x3fff) | 0x8000,
                random_int(0, 0xffff),
                random_int(0, 0xffff),
                random_int(0, 0xffff)
            );
        } catch (Exception $e) {
            throw new CannotGenerateRandomIntException('Cannot generate random integer', $e);
        }
    }

    /**
     * Url safe for base64
     */
    private function base64UrlSafeDecode(string $string): string
    {
        $base64 = strtr($string, '-_', '+/');

        return base64_decode($base64);
    }
}


================================================
FILE: src/Esia/Signer/AbstractSignerPKCS7.php
================================================
<?php

namespace Esia\Signer;

use Esia\Signer\Exceptions\CannotReadCertificateException;
use Esia\Signer\Exceptions\CannotReadPrivateKeyException;
use Esia\Signer\Exceptions\NoSuchCertificateFileException;
use Esia\Signer\Exceptions\NoSuchKeyFileException;
use Esia\Signer\Exceptions\NoSuchTmpDirException;
use Esia\Signer\Exceptions\SignFailException;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;

abstract class AbstractSignerPKCS7
{
    use LoggerAwareTrait;

    /**
     * Path to the certificate
     *
     * @var string
     */
    protected $certPath;

    /**
     * Path to the private key
     *
     * @var string
     */
    protected $privateKeyPath;

    /**
     * Password for the private key
     *
     * @var string
     */
    protected $privateKeyPassword;

    /**
     * SignerPKCS7 constructor.
     */
    public function __construct(
        string $certPath,
        string $privateKeyPath,
        ?string $privateKeyPassword,
        string $tmpPath
    ) {
        $this->certPath = $certPath;
        $this->privateKeyPath = $privateKeyPath;
        $this->privateKeyPassword = $privateKeyPassword;
        $this->tmpPath = $tmpPath;
        $this->logger = new NullLogger();
    }

    /**
     * Temporary directory for message signing (must me writable)
     *
     * @var string
     */
    protected $tmpPath;

    /**
     * @throws SignFailException
     */
    protected function checkFilesExists(): void
    {
        if (!file_exists($this->certPath)) {
            throw new NoSuchCertificateFileException('Certificate does not exist');
        }
        if (!is_readable($this->certPath)) {
            throw new CannotReadCertificateException('Cannot read the certificate');
        }
        if (!file_exists($this->privateKeyPath)) {
            throw new NoSuchKeyFileException('Private key does not exist');
        }
        if (!is_readable($this->privateKeyPath)) {
            throw new CannotReadPrivateKeyException('Cannot read the private key');
        }
        if (!file_exists($this->tmpPath)) {
            throw new NoSuchTmpDirException('Temporary folder is not found');
        }
        if (!is_writable($this->tmpPath)) {
            throw new NoSuchTmpDirException('Temporary folder is not writable');
        }
    }

    /**
     * Generate random unique string
     */
    protected function getRandomString(): string
    {
        return md5(uniqid(mt_rand(), true));
    }

    /**
     * Url safe for base64
     */
    protected function urlSafe(string $string): string
    {
        return rtrim(strtr(trim($string), '+/', '-_'), '=');
    }
}


================================================
FILE: src/Esia/Signer/CliSignerPKCS7.php
================================================
<?php

namespace Esia\Signer;

use Esia\Signer\Exceptions\SignFailException;

class CliSignerPKCS7 extends AbstractSignerPKCS7 implements SignerInterface
{
    /**
     * @throws SignFailException
     */
    public function sign(string $message): string
    {
        $this->checkFilesExists();

        // random unique directories for sign
        $messageFile = $this->tmpPath . DIRECTORY_SEPARATOR . $this->getRandomString();
        $signFile = $this->tmpPath . DIRECTORY_SEPARATOR . $this->getRandomString();
        file_put_contents($messageFile, $message);

        $this->run(
            'openssl ' .
            'smime -engine gost -sign -binary -outform DER -noattr ' .
            '-signer ' . escapeshellarg($this->certPath) . ' ' .
            '-inkey ' . escapeshellarg($this->privateKeyPath) . ' ' .
            '-passin ' . escapeshellarg('pass:' . $this->privateKeyPassword) . ' ' .
            '-in ' . escapeshellarg($messageFile) . ' ' .
            '-out ' . escapeshellarg($signFile)
        );

        $signed = file_get_contents($signFile);
        if ($signed === false) {
            $message = sprintf('cannot read %s file', $signFile);
            $this->logger->error($message);
            throw new SignFailException($message);
        }
        $sign = $this->urlSafe(base64_encode($signed));

        unlink($signFile);
        unlink($messageFile);
        return $sign;
    }

    /**
     * @throws SignFailException
     */
    private function run(string $command): void
    {
        $process = proc_open(
            $command,
            [
                ['pipe', 'w'], // stdout
                ['pipe', 'w'], // stderr
            ],
            $pipes
        );

        $result = stream_get_contents($pipes[0]);
        fclose($pipes[0]);

        $errors = stream_get_contents($pipes[1]);
        fclose($pipes[1]);

        $code = proc_close($process);
        if (0 !== $code || $result === false) {
            $errors = $errors ?: 'unknown';
            $this->logger->error('Sign fail');
            $this->logger->error('SSL error: ' . $errors);
            throw new SignFailException($errors);
        }
    }
}


================================================
FILE: src/Esia/Signer/Exceptions/CannotGenerateRandomIntException.php
================================================
<?php

namespace Esia\Signer\Exceptions;

class CannotGenerateRandomIntException extends SignFailException
{
}


================================================
FILE: src/Esia/Signer/Exceptions/CannotReadCertificateException.php
================================================
<?php

namespace Esia\Signer\Exceptions;

class CannotReadCertificateException extends SignFailException
{
}


================================================
FILE: src/Esia/Signer/Exceptions/CannotReadPrivateKeyException.php
================================================
<?php

namespace Esia\Signer\Exceptions;

class CannotReadPrivateKeyException extends SignFailException
{
}


================================================
FILE: src/Esia/Signer/Exceptions/NoSuchCertificateFileException.php
================================================
<?php

namespace Esia\Signer\Exceptions;

class NoSuchCertificateFileException extends SignFailException
{
}


================================================
FILE: src/Esia/Signer/Exceptions/NoSuchKeyFileException.php
================================================
<?php

namespace Esia\Signer\Exceptions;

class NoSuchKeyFileException extends SignFailException
{
}


================================================
FILE: src/Esia/Signer/Exceptions/NoSuchTmpDirException.php
================================================
<?php

namespace Esia\Signer\Exceptions;

class NoSuchTmpDirException extends SignFailException
{
}


================================================
FILE: src/Esia/Signer/Exceptions/SignFailException.php
================================================
<?php

namespace Esia\Signer\Exceptions;

use Esia\Exceptions\AbstractEsiaException;

class SignFailException extends AbstractEsiaException
{
    protected function getMessageForCode(int $code): string
    {
        return 'Signing is failed';
    }
}


================================================
FILE: src/Esia/Signer/SignerInterface.php
================================================
<?php

namespace Esia\Signer;

use Esia\Signer\Exceptions\SignFailException;

interface SignerInterface
{
    /**
     * @throws SignFailException
     */
    public function sign(string $message): string;
}


================================================
FILE: src/Esia/Signer/SignerPKCS7.php
================================================
<?php

namespace Esia\Signer;

use Esia\Signer\Exceptions\CannotReadCertificateException;
use Esia\Signer\Exceptions\CannotReadPrivateKeyException;
use Esia\Signer\Exceptions\SignFailException;

class SignerPKCS7 extends AbstractSignerPKCS7 implements SignerInterface
{
    private $pkcs7Flags = PKCS7_DETACHED;

    public function addPKCS7Flag(int $pkcs7Flag): void
    {
        $this->pkcs7Flags |= $pkcs7Flag;
    }

    /**
     * @throws SignFailException
     */
    public function sign(string $message): string
    {
        $this->checkFilesExists();

        $certContent = file_get_contents($this->certPath);
        $keyContent = file_get_contents($this->privateKeyPath);

        $cert = openssl_x509_read($certContent);

        if ($cert === false) {
            throw new CannotReadCertificateException('Cannot read the certificate: ' . openssl_error_string());
        }

        $this->logger->debug('Cert: ' . print_r($cert, true), ['cert' => $cert]);

        $privateKey = openssl_pkey_get_private($keyContent, $this->privateKeyPassword);

        if ($privateKey === false) {
            throw new CannotReadPrivateKeyException('Cannot read the private key: ' . openssl_error_string());
        }

        $this->logger->debug('Private key: : ' . print_r($privateKey, true), ['privateKey' => $privateKey]);

        // random unique directories for sign
        $messageFile = $this->tmpPath . DIRECTORY_SEPARATOR . $this->getRandomString();
        $signFile = $this->tmpPath . DIRECTORY_SEPARATOR . $this->getRandomString();
        file_put_contents($messageFile, $message);
        $signResult = openssl_pkcs7_sign(
            $messageFile,
            $signFile,
            $cert,
            $privateKey,
            [],
            $this->pkcs7Flags
        );

        if ($signResult) {
            $this->logger->debug('Sign success');
        } else {
            $this->logger->error('Sign fail');
            $this->logger->error('SSL error: ' . openssl_error_string());
            throw new SignFailException('Cannot sign the message');
        }

        $signed = file_get_contents($signFile);

        # split by section
        $signed = explode("\n\n", $signed);

        # get third section which contains sign and join into one line
        $sign = str_replace("\n", '', $this->urlSafe($signed[3]));

        unlink($signFile);
        unlink($messageFile);

        return $sign;
    }
}


================================================
FILE: tests/.configure-gost-openssl.sh
================================================
sed -i '1iopenssl_conf=openssl_def' /usr/lib/ssl/openssl.cnf
tee -a /usr/lib/ssl/openssl.cnf <<EOF

[openssl_def]
engines = engine_section

[engine_section]
gost = gost_section

[gost_section]
engine_id = gost
dynamic_path = /usr/lib/x86_64-linux-gnu/engines-1.1/gost.so
default_algorithms = ALL
CRYPT_PARAMS = id-Gost28147-89-CryptoPro-A-ParamSet

EOF


================================================
FILE: tests/_bootstrap.php
================================================
<?php
// This is global bootstrap for autoloading
require_once __DIR__ . '/../vendor/autoload.php';


================================================
FILE: tests/_data/server-gost.crt
================================================
-----BEGIN CERTIFICATE-----
MIIBnDCCAUmgAwIBAgIJAP+MfsgMHfr6MAoGBiqFAwICAwUAMCgxCzAJBgNVBAYT
AlJVMRkwFwYDVQQDDBBUZXN0ZXIgR09TVCAyMDAxMCAXDTE4MDUyMjA5MzQ1NloY
DzIwNjgwNTA5MDkzNDU2WjAoMQswCQYDVQQGEwJSVTEZMBcGA1UEAwwQVGVzdGVy
IEdPU1QgMjAwMTBjMBwGBiqFAwICEzASBgcqhQMCAiMBBgcqhQMCAh4BA0MABEDL
FFQ4czv3WOfm3+6m84epehMScB/6vJtrLgodrpdByvtk3LJyFoujKFNamQlzeJMy
1Pfr/62l+8BAR4x0uMFIo1AwTjAdBgNVHQ4EFgQUe3YXXQwYzL0r+vo+FHrvc+O7
5J8wHwYDVR0jBBgwFoAUe3YXXQwYzL0r+vo+FHrvc+O75J8wDAYDVR0TBAUwAwEB
/zAKBgYqhQMCAgMFAANBANZ+qRGMoXesUdgW2nDiFxFoJpsWSW/Njr3QY98VY/F6
/Q5xYGGd4pvnv71Mp2FHSR6YjWDR3/yV4q6z1TBQrDE=
-----END CERTIFICATE-----


================================================
FILE: tests/_data/server-gost.key
================================================
-----BEGIN PRIVATE KEY-----
MEMCAQAwHAYGKoUDAgITMBIGByqFAwICIwEGByqFAwICHgEEIOjpBFHdMSI7NioS
EjRMxGuikBUuBmvzIm+2JP9Z5yrK
-----END PRIVATE KEY-----


================================================
FILE: tests/_data/server.crt
================================================
-----BEGIN CERTIFICATE-----
MIICATCCAWoCCQDuV6K/drn++DANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJS
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTE1MTAwNjA1MjQwM1oXDTI1MTAwMzA1MjQwM1owRTELMAkG
A1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwKEW
Moa/dyEGJ1OI8M07lP0r6R6Js05RoarMKJpVRbkmiO9byqcbCEiPJLZfdcNsORGZ
c9L09YBbUjJkpy4TY+qzNOpQMoScfeHA/5LzlXqzOdDapYUuXkxHQmOfAHBR3any
UuVuCBJABp0jxxMVTXxKtxgpfqLH2Jmr31yu6vMCAwEAATANBgkqhkiG9w0BAQUF
AAOBgQCJwsCDtw4IoPQiVw1fpEc8aS/QlujwChvyaTJUJ96cWHQs5hz7M23rPTib
TZ4ooJ6jbP/e7vaxpXpXShNBnnP3RVCOKX0P0t1XS1nCvr651KXlIwNWTXZxSZ72
vl5ySU71uOSaVPjicxIaPG93IFe5X7VTV4QDanSZqQqLwZsUGQ==
-----END CERTIFICATE-----


================================================
FILE: tests/_data/server.csr
================================================
-----BEGIN CERTIFICATE REQUEST-----
MIIBhDCB7gIBADBFMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEh
MB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQDAoRYyhr93IQYnU4jwzTuU/SvpHomzTlGhqswomlVFuSaI
71vKpxsISI8ktl91w2w5EZlz0vT1gFtSMmSnLhNj6rM06lAyhJx94cD/kvOVerM5
0NqlhS5eTEdCY58AcFHdqfJS5W4IEkAGnSPHExVNfEq3GCl+osfYmavfXK7q8wID
AQABoAAwDQYJKoZIhvcNAQEFBQADgYEAn8N4/LFU6fUO4wdBPB8uWV7+rHQURFAU
4qzsmcl+9l2EsHsloMRZzqKnJJHsJETxfb1zzzg4dve2bJy+50EklRMylnlG3nOi
7QKdlHyCmq9YKcra3OiyeYsQ5FnpY/WEbwFoWLt9WYwHHNrbWzSpEj8Mb7DzP8S/
kMPnUe1fH/Q=
-----END CERTIFICATE REQUEST-----


================================================
FILE: tests/_data/server.key
================================================
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,332C0B168FE46450

T7FYR235atS5fngeCLDhNrTYRz4BBMAPcTbLy5QrMMo39hjOclqol+D/QkYY7BXC
xerxuE7cGvQEKwr3PAjNOZ8PREDR7saGZhrPNoup1lWjtkR9RiJrNcg7Ya7fCCX4
a+dsDAjUt+gxg1IfMd39nQR2gO3VxYjdNFRwwaFPQpYMotSkCutUCTC8BbtWEm5G
k8bNuoJPvZfgqyZSbImlXvlht6JoUBYHoJHsin/drYxCZNDDBPe+d6yYaza8Qh0g
5a1/btkSmy8Sq5Cd2gbGICxOKDRItASvO7nBauPbGJ/N7+wt/WwT8g9XGRugzNOj
n+a9Kmqs6NwmFmaqeAZBTp3n+EmKii5tdXPmOkF9oMRLRWJsAUkzkjwPws1i3WfX
GQR0n8MmcDcLdQ9jCPV8SMp2VR49EjKInh3NRlfBwpSJCaoEAzt9Je0rj4b0eYFO
pMoNjSpWwVsLPJ3GTW0Pz8nUIlhOppZI2ARHGRSfwjtY8/IBPX8Ja5gGjDu/QT8u
EIAG+bFCk5JgYa+Sz0EX5Ok9fWymqDguF4Mr/w4Xsq0vvT3wZwq6lJzSduv+W6/l
cwncRCXyg6HLbuW+5jKPcZEF3K70zXTktLyDjZlHzqoIfBgdU/cah+X/g4N5Swfx
vjWAlUNa5YjBk6kuuTwZ8GvlBGjrYBmN4zSHAXCngGtYb+oV06hDECHVxRBu3J+2
yQXYnX8W/PfAAPxWIX8fXGzaVjrGh4tVicI1kHrma0vaCOtEKVamg9wsvSTw+YWn
TDThQ+rESGmt5PPmtlrLXhHmYBp8Xd2b95iwhMHvDYNVmRLnESH/Og==
-----END RSA PRIVATE KEY-----


================================================
FILE: tests/_support/Helper/Unit.php
================================================
<?php
namespace Helper;
// here you can define custom actions
// all public methods declared in helper class will be available in $I

use Codeception\Module;
use Codeception\TestCase;

class Unit extends Module
{
}


================================================
FILE: tests/_support/UnitTester.php
================================================
<?php


/**
 * Inherited Methods
 * @method void wantToTest($text)
 * @method void wantTo($text)
 * @method void execute($callable)
 * @method void expectTo($prediction)
 * @method void expect($prediction)
 * @method void amGoingTo($argumentation)
 * @method void am($role)
 * @method void lookForwardTo($achieveValue)
 * @method void comment($description)
 * @method \Codeception\Lib\Friend haveFriend($name, $actorClass = null)
 *
 * @SuppressWarnings(PHPMD)
*/
class UnitTester extends \Codeception\Actor
{
    use _generated\UnitTesterActions;

   /**
    * Define custom actions here
    */
}


================================================
FILE: tests/_support/_generated/UnitTesterActions.php
================================================
<?php
//[STAMP] c2c9446f63b7a0bf8b43e42b9b1b6f87
namespace _generated;

// This class was automatically generated by build task
// You should not change it manually as it will be overwritten on next build
// @codingStandardsIgnoreFile

use Codeception\Scenario;
use Codeception\Step\Action;

trait UnitTesterActions
{
    /**
     * @return Scenario
     */
    abstract protected function getScenario();


    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that two variables are equal. If you're comparing floating-point values,
     * you can specify the optional "delta" parameter which dictates how great of a precision
     * error are you willing to tolerate in order to consider the two values equal.
     *
     * Regular example:
     * ```php
     * <?php
     * $I->assertEquals(5, $element->getChildrenCount());
     * ```
     *
     * Floating-point example:
     * ```php
     * <?php
     * $I->assertEquals(0.3, $calculator->add(0.1, 0.2), 'Calculator should add the two numbers correctly.', 0.01);
     * ```
     *
     * @param        $expected
     * @param        $actual
     * @param string $message
     * @param float $delta
     * @see \Codeception\Module\Asserts::assertEquals()
     */
    public function assertEquals($expected, $actual, $message = null, $delta = null) {
        return $this->getScenario()->runStep(new Action('assertEquals', func_get_args()));
    }


    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that two variables are not equal. If you're comparing floating-point values,
     * you can specify the optional "delta" parameter which dictates how great of a precision
     * error are you willing to tolerate in order to consider the two values not equal.
     *
     * Regular example:
     * ```php
     * <?php
     * $I->assertNotEquals(0, $element->getChildrenCount());
     * ```
     *
     * Floating-point example:
     * ```php
     * <?php
     * $I->assertNotEquals(0.4, $calculator->add(0.1, 0.2), 'Calculator should add the two numbers correctly.', 0.01);
     * ```
     *
     * @param        $expected
     * @param        $actual
     * @param string $message
     * @param float $delta
     * @see \Codeception\Module\Asserts::assertNotEquals()
     */
    public function assertNotEquals($expected, $actual, $message = null, $delta = null) {
        return $this->getScenario()->runStep(new Action('assertNotEquals', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that two variables are same
     *
     * @param        $expected
     * @param        $actual
     * @param string $message
     * @see \Codeception\Module\Asserts::assertSame()
     */
    public function assertSame($expected, $actual, $message = null) {
        return $this->getScenario()->runStep(new Action('assertSame', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that two variables are not same
     *
     * @param        $expected
     * @param        $actual
     * @param string $message
     * @see \Codeception\Module\Asserts::assertNotSame()
     */
    public function assertNotSame($expected, $actual, $message = null) {
        return $this->getScenario()->runStep(new Action('assertNotSame', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that actual is greater than expected
     *
     * @param        $expected
     * @param        $actual
     * @param string $message
     * @see \Codeception\Module\Asserts::assertGreaterThan()
     */
    public function assertGreaterThan($expected, $actual, $message = null) {
        return $this->getScenario()->runStep(new Action('assertGreaterThan', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that actual is greater or equal than expected
     *
     * @param        $expected
     * @param        $actual
     * @param string $message
     * @see \Codeception\Module\Asserts::assertGreaterThanOrEqual()
     */
    public function assertGreaterThanOrEqual($expected, $actual, $message = null) {
        return $this->getScenario()->runStep(new Action('assertGreaterThanOrEqual', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that actual is less than expected
     *
     * @param        $expected
     * @param        $actual
     * @param string $message
     * @see \Codeception\Module\Asserts::assertLessThan()
     */
    public function assertLessThan($expected, $actual, $message = null) {
        return $this->getScenario()->runStep(new Action('assertLessThan', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that actual is less or equal than expected
     *
     * @param        $expected
     * @param        $actual
     * @param string $message
     * @see \Codeception\Module\Asserts::assertLessThanOrEqual()
     */
    public function assertLessThanOrEqual($expected, $actual, $message = null) {
        return $this->getScenario()->runStep(new Action('assertLessThanOrEqual', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that haystack contains needle
     *
     * @param        $needle
     * @param        $haystack
     * @param string $message
     * @see \Codeception\Module\Asserts::assertContains()
     */
    public function assertContains($needle, $haystack, $message = null) {
        return $this->getScenario()->runStep(new Action('assertContains', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that haystack doesn't contain needle.
     *
     * @param        $needle
     * @param        $haystack
     * @param string $message
     * @see \Codeception\Module\Asserts::assertNotContains()
     */
    public function assertNotContains($needle, $haystack, $message = null) {
        return $this->getScenario()->runStep(new Action('assertNotContains', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that string match with pattern
     *
     * @param string $pattern
     * @param string $string
     * @param string $message
     * @see \Codeception\Module\Asserts::assertRegExp()
     */
    public function assertRegExp($pattern, $string, $message = null) {
        return $this->getScenario()->runStep(new Action('assertRegExp', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that string not match with pattern
     *
     * @param string $pattern
     * @param string $string
     * @param string $message
     * @see \Codeception\Module\Asserts::assertNotRegExp()
     */
    public function assertNotRegExp($pattern, $string, $message = null) {
        return $this->getScenario()->runStep(new Action('assertNotRegExp', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that a string starts with the given prefix.
     *
     * @param string $prefix
     * @param string $string
     * @param string $message
     * @see \Codeception\Module\Asserts::assertStringStartsWith()
     */
    public function assertStringStartsWith($prefix, $string, $message = null) {
        return $this->getScenario()->runStep(new Action('assertStringStartsWith', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that a string doesn't start with the given prefix.
     *
     * @param string $prefix
     * @param string $string
     * @param string $message
     * @see \Codeception\Module\Asserts::assertStringStartsNotWith()
     */
    public function assertStringStartsNotWith($prefix, $string, $message = null) {
        return $this->getScenario()->runStep(new Action('assertStringStartsNotWith', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that variable is empty.
     *
     * @param        $actual
     * @param string $message
     * @see \Codeception\Module\Asserts::assertEmpty()
     */
    public function assertEmpty($actual, $message = null) {
        return $this->getScenario()->runStep(new Action('assertEmpty', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that variable is not empty.
     *
     * @param        $actual
     * @param string $message
     * @see \Codeception\Module\Asserts::assertNotEmpty()
     */
    public function assertNotEmpty($actual, $message = null) {
        return $this->getScenario()->runStep(new Action('assertNotEmpty', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that variable is NULL
     *
     * @param        $actual
     * @param string $message
     * @see \Codeception\Module\Asserts::assertNull()
     */
    public function assertNull($actual, $message = null) {
        return $this->getScenario()->runStep(new Action('assertNull', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that variable is not NULL
     *
     * @param        $actual
     * @param string $message
     * @see \Codeception\Module\Asserts::assertNotNull()
     */
    public function assertNotNull($actual, $message = null) {
        return $this->getScenario()->runStep(new Action('assertNotNull', func_get_args()));
    }


    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that condition is positive.
     *
     * @param        $condition
     * @param string $message
     * @see \Codeception\Module\Asserts::assertTrue()
     */
    public function assertTrue($condition, $message = null)
    {
        return $this->getScenario()->runStep(new Action('assertTrue', func_get_args()));
    }


    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that the condition is NOT true (everything but true)
     *
     * @param        $condition
     * @param string $message
     * @see \Codeception\Module\Asserts::assertNotTrue()
     */
    public function assertNotTrue($condition, $message = null)
    {
        return $this->getScenario()->runStep(new Action('assertNotTrue', func_get_args()));
    }


    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that condition is negative.
     *
     * @param        $condition
     * @param string $message
     * @see \Codeception\Module\Asserts::assertFalse()
     */
    public function assertFalse($condition, $message = null)
    {
        return $this->getScenario()->runStep(new Action('assertFalse', func_get_args()));
    }


    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that the condition is NOT false (everything but false)
     *
     * @param        $condition
     * @param string $message
     * @see \Codeception\Module\Asserts::assertNotFalse()
     */
    public function assertNotFalse($condition, $message = null)
    {
        return $this->getScenario()->runStep(new Action('assertNotFalse', func_get_args()));
    }


    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks if file exists
     *
     * @param string $filename
     * @param string $message
     * @see \Codeception\Module\Asserts::assertFileExists()
     */
    public function assertFileExists($filename, $message = null)
    {
        return $this->getScenario()->runStep(new Action('assertFileExists', func_get_args()));
    }


    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks if file doesn't exist
     *
     * @param string $filename
     * @param string $message
     * @see \Codeception\Module\Asserts::assertFileNotExists()
     */
    public function assertFileNotExists($filename, $message = null) {
        return $this->getScenario()->runStep(new Action('assertFileNotExists', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * @param $expected
     * @param $actual
     * @param $description
     * @see \Codeception\Module\Asserts::assertGreaterOrEquals()
     */
    public function assertGreaterOrEquals($expected, $actual, $description = null) {
        return $this->getScenario()->runStep(new Action('assertGreaterOrEquals', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * @param $expected
     * @param $actual
     * @param $description
     * @see \Codeception\Module\Asserts::assertLessOrEquals()
     */
    public function assertLessOrEquals($expected, $actual, $description = null) {
        return $this->getScenario()->runStep(new Action('assertLessOrEquals', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * @param $actual
     * @param $description
     * @see \Codeception\Module\Asserts::assertIsEmpty()
     */
    public function assertIsEmpty($actual, $description = null) {
        return $this->getScenario()->runStep(new Action('assertIsEmpty', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * @param $key
     * @param $actual
     * @param $description
     * @see \Codeception\Module\Asserts::assertArrayHasKey()
     */
    public function assertArrayHasKey($key, $actual, $description = null) {
        return $this->getScenario()->runStep(new Action('assertArrayHasKey', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * @param $key
     * @param $actual
     * @param $description
     * @see \Codeception\Module\Asserts::assertArrayNotHasKey()
     */
    public function assertArrayNotHasKey($key, $actual, $description = null) {
        return $this->getScenario()->runStep(new Action('assertArrayNotHasKey', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Checks that array contains subset.
     *
     * @param array  $subset
     * @param array  $array
     * @param bool   $strict
     * @param string $message
     * @see \Codeception\Module\Asserts::assertArraySubset()
     */
    public function assertArraySubset($subset, $array, $strict = null, $message = null) {
        return $this->getScenario()->runStep(new Action('assertArraySubset', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * @param $expectedCount
     * @param $actual
     * @param $description
     * @see \Codeception\Module\Asserts::assertCount()
     */
    public function assertCount($expectedCount, $actual, $description = null) {
        return $this->getScenario()->runStep(new Action('assertCount', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * @param $class
     * @param $actual
     * @param $description
     * @see \Codeception\Module\Asserts::assertInstanceOf()
     */
    public function assertInstanceOf($class, $actual, $description = null) {
        return $this->getScenario()->runStep(new Action('assertInstanceOf', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * @param $class
     * @param $actual
     * @param $description
     * @see \Codeception\Module\Asserts::assertNotInstanceOf()
     */
    public function assertNotInstanceOf($class, $actual, $description = null) {
        return $this->getScenario()->runStep(new Action('assertNotInstanceOf', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * @param $type
     * @param $actual
     * @param $description
     * @see \Codeception\Module\Asserts::assertInternalType()
     */
    public function assertInternalType($type, $actual, $description = null) {
        return $this->getScenario()->runStep(new Action('assertInternalType', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Fails the test with message.
     *
     * @param $message
     * @see \Codeception\Module\Asserts::fail()
     */
    public function fail($message) {
        return $this->getScenario()->runStep(new Action('fail', func_get_args()));
    }

 
    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Handles and checks exception called inside callback function.
     * Either exception class name or exception instance should be provided.
     *
     * ```php
     * <?php
     * $I->expectException(MyException::class, function() {
     *     $this->doSomethingBad();
     * });
     *
     * $I->expectException(new MyException(), function() {
     *     $this->doSomethingBad();
     * });
     * ```
     * If you want to check message or exception code, you can pass them with exception instance:
     * ```php
     * <?php
     * // will check that exception MyException is thrown with "Don't do bad things" message
     * $I->expectException(new MyException("Don't do bad things"), function() {
     *     $this->doSomethingBad();
     * });
     * ```
     *
     * @param $exception string or \Exception
     * @param $callback
     *
     * @deprecated Use expectThrowable instead
     * @see \Codeception\Module\Asserts::expectException()
     */
    public function expectException($exception, $callback)
    {
        return $this->getScenario()->runStep(new Action('expectException', func_get_args()));
    }


    /**
     * [!] Method is generated. Documentation taken from corresponding module.
     *
     * Handles and checks throwables (Exceptions/Errors) called inside the callback function.
     * Either throwable class name or throwable instance should be provided.
     *
     * ```php
     * <?php
     * $I->expectThrowable(MyThrowable::class, function() {
     *     $this->doSomethingBad();
     * });
     *
     * $I->expectThrowable(new MyException(), function() {
     *     $this->doSomethingBad();
     * });
     * ```
     * If you want to check message or throwable code, you can pass them with throwable instance:
     * ```php
     * <?php
     * // will check that throwable MyError is thrown with "Don't do bad things" message
     * $I->expectThrowable(new MyError("Don't do bad things"), function() {
     *     $this->doSomethingBad();
     * });
     * ```
     *
     * @param $throwable string or \Throwable
     * @param $callback
     * @see \Codeception\Module\Asserts::expectThrowable()
     */
    public function expectThrowable($throwable, $callback)
    {
        return $this->getScenario()->runStep(new Action('expectThrowable', func_get_args()));
    }
}


================================================
FILE: tests/unit/ConfigTest.php
================================================
<?php

namespace tests\unit;

use Codeception\Test\Unit;
use Esia\Config;
use Esia\Exceptions\InvalidConfigurationException;

/**
 * Class ConfigTest
 *
 * @coversDefaultClass \Esia\Config
 */
class ConfigTest extends Unit
{
    /**
     * Getter for scope string
     *
     * @throws \Esia\Exceptions\InvalidConfigurationException
     */
    public function testGetScopeString(): void
    {
        $config = new Config([
            'clientId' => 'test',
            'redirectUrl' => 'http://google.com',
            'privateKeyPath' => '/tmp',
            'certPath' => '/tmp',
            'scope' => ['test', 'test2', 'test3'],
        ]);

        $this->assertSame('test test2 test3', $config->getScopeString());
    }

    /**
     * Data provider for @see ConfigTest::testConstruct()
     *
     * @return array
     */
    public function dataProviderForConstructor(): array
    {
        return [
            'min' => [
                [
                    'clientId' => 'test',
                    'redirectUrl' => 'http://google.com',
                    'privateKeyPath' => '/tmp',
                    'certPath' => '/tmp',
                    'scope' => ['test', 'test2', 'test3'],
                ],
                null,
            ],
            'max' => [
                [
                    'clientId' => 'test',
                    'redirectUrl' => 'http://google.com',
                    'privateKeyPath' => '/tmp',
                    'certPath' => '/tmp',
                    'portalUrl' => 'google.com',
                    'tokenUrlPath' => 'test',
                    'codeUrlPath' => 'test',
                    'personUrlPath' => 'test',
                    'logoutUrlPath' => 'test',
                    'privateKeyPassword' => 'test',
                    'oid' => 'test',
                    'responseType' => 'test',
                    'accessType' => 'test',
                    'tmpPath' => 'test',
                    'token' => 'test',
                    'scope' => ['test', 'test2', 'test3'],
                ],
                null,
            ],
            'No cert path' => [
                [
                    'clientId' => 'test',
                    'redirectUrl' => 'http://google.com',
                    'privateKeyPath' => '/tmp',
                    'scope' => ['test', 'test2', 'test3'],
                ],
                InvalidConfigurationException::class,
            ],
            'No private key path' => [
                [
                    'clientId' => 'test',
                    'redirectUrl' => 'http://google.com',
                    'certPath' => '/tmp',
                    'scope' => ['test', 'test2', 'test3'],
                ],
                InvalidConfigurationException::class,
            ],
            'No redirect url' => [
                [
                    'clientId' => 'test',
                    'privateKeyPath' => '/tmp',
                    'certPath' => '/tmp',
                    'scope' => ['test', 'test2', 'test3'],
                ],
                InvalidConfigurationException::class,
            ],
            'No client id' => [
                [
                    'redirectUrl' => 'http://google.com',
                    'privateKeyPath' => '/tmp',
                    'certPath' => '/tmp',
                    'scope' => ['test', 'test2', 'test3'],
                ],
                InvalidConfigurationException::class,
            ],
            'invalid scope' => [
                [
                    'redirectUrl' => 'http://google.com',
                    'privateKeyPath' => '/tmp',
                    'certPath' => '/tmp',
                    'scope' => 'test test2 test3',
                ],
                InvalidConfigurationException::class,
            ],
        ];
    }

    /**
     * @param $config
     * @param string|null $expectedException
     * @throws \Esia\Exceptions\InvalidConfigurationException
     *
     * @dataProvider dataProviderForConstructor
     */
    public function testConstruct($config, string $expectedException = null): void
    {
        if ($expectedException) {
            $this->expectException($expectedException);
        }

        new Config($config);
    }

    /**
     * @throws InvalidConfigurationException
     */
    public function testGetTokenUrl(): void
    {
        $config = new Config([
            'clientId' => 'test',
            'redirectUrl' => 'http://google.com',
            'privateKeyPath' => '/tmp',
            'certPath' => '/tmp',
            'portalUrl' => 'https://google.com/',
            'tokenUrlPath' => 'test',
            'scope' => ['test', 'test2', 'test3'],
        ]);

        $this->assertSame('https://google.com/test', $config->getTokenUrl());
    }

    /**
     * @throws InvalidConfigurationException
     */
    public function testGetCodeUrl(): void
    {
        $config = new Config([
            'clientId' => 'test',
            'redirectUrl' => 'http://google.com',
            'privateKeyPath' => '/tmp',
            'certPath' => '/tmp',
            'portalUrl' => 'https://google.com/',
            'codeUrlPath' => 'test',
            'scope' => ['test', 'test2', 'test3'],
        ]);

        $this->assertSame('https://google.com/test', $config->getCodeUrl());
    }

    /**
     * @throws InvalidConfigurationException
     */
    public function testGetPersonUrl(): void
    {
        $config = new Config([
            'clientId' => 'test',
            'redirectUrl' => 'http://google.com',
            'privateKeyPath' => '/tmp',
            'certPath' => '/tmp',
            'portalUrl' => 'https://google.com/',
            'personUrlPath' => 'test',
            'oid' => 'test',
            'scope' => ['test', 'test2', 'test3'],
        ]);

        $this->assertSame('https://google.com/test/test', $config->getPersonUrl());
    }
    /**
     * @throws InvalidConfigurationException
     */
    public function testGetPersonUrlWithoutOid(): void
    {
        $config = new Config([
            'clientId' => 'test',
            'redirectUrl' => 'http://google.com',
            'privateKeyPath' => '/tmp',
            'certPath' => '/tmp',
            'portalUrl' => 'https://google.com/',
            'personUrlPath' => 'test',
            'scope' => ['test', 'test2', 'test3'],
        ]);

        $this->expectException(InvalidConfigurationException::class);
        $this->assertSame('https://google.com/test/test', $config->getPersonUrl());
    }
    /**
     * @throws InvalidConfigurationException
     */
    public function testGetLogoutUrl(): void
    {
        $config = new Config([
            'clientId' => 'test',
            'redirectUrl' => 'http://google.com',
            'privateKeyPath' => '/tmp',
            'certPath' => '/tmp',
            'portalUrl' => 'https://google.com/',
            'logoutUrlPath' => 'test',
            'scope' => ['test', 'test2', 'test3'],
        ]);

        $this->assertSame('https://google.com/test', $config->getLogoutUrl());
    }
}


================================================
FILE: tests/unit/Http/GuzzleHttpClientTest.php
================================================
<?php

namespace tests\unit\Http;

use Codeception\Test\Unit;
use Esia\Http\GuzzleHttpClient;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use HttpException;
use Psr\Http\Client\ClientExceptionInterface;

/**
 * Class GuzzleHttpClientTest
 *
 * @coversDefaultClass \Esia\Http\GuzzleHttpClient
 */
class GuzzleHttpClientTest extends Unit
{
    /**
     * @throws ClientExceptionInterface
     * @throws HttpException
     */
    public function testSendRequest(): void
    {
        $mock = new MockHandler([
            new Response(),
            new RequestException('Error Communicating with Server', new Request('GET', 'test'))
        ]);

        $handler = HandlerStack::create($mock);
        $guzzleClient = new Client(['handler' => $handler]);

        $client = new GuzzleHttpClient($guzzleClient);

        $response = $client->sendRequest(new Request('GET', '/'));

        self::assertSame(200, $response->getStatusCode());

        $this->expectException(ClientExceptionInterface::class);
        $client->sendRequest(new Request('GET', '/'));
    }
}


================================================
FILE: tests/unit/OpenIdCliOpensslTest.php
================================================
<?php

namespace tests\unit;

use Esia\Config;
use Esia\Exceptions\AbstractEsiaException;
use Esia\Exceptions\InvalidConfigurationException;
use Esia\OpenId;
use Esia\Signer\CliSignerPKCS7;
use GuzzleHttp\Psr7\Response;

class OpenIdCliOpensslTest extends OpenIdTest
{
    /**
     * @throws InvalidConfigurationException
     */
    public function setUp(): void
    {
        $this->config = [
            'clientId' => 'INSP03211',
            'redirectUrl' => 'http://my-site.com/response.php',
            'portalUrl' => 'https://esia-portal1.test.gosuslugi.ru/',
            'privateKeyPath' => codecept_data_dir('server-gost.key'),
            'privateKeyPassword' => 'test',
            'certPath' => codecept_data_dir('server-gost.crt'),
            'tmpPath' => codecept_log_dir(),
        ];

        $config = new Config($this->config);

        $this->openId = new OpenId($config);
        $this->openId->setSigner(new CliSignerPKCS7(
            $this->config['certPath'],
            $this->config['privateKeyPath'],
            $this->config['privateKeyPassword'],
            $this->config['tmpPath']
        ));
    }

    /**
     * @throws AbstractEsiaException
     * @throws InvalidConfigurationException
     */
    public function testGetToken(): void
    {
        $config = new Config($this->config);

        $oid = '123';
        $oidBase64 = base64_encode('{ "urn:esia:sbj_id" : ' . $oid . '}');

        $client = $this->buildClientWithResponses([
            new Response(200, [], '{ "access_token": "test.' . $oidBase64 . '.test"}'),
        ]);
        $openId = new OpenId($config, $client);
        $openId->setSigner(new CliSignerPKCS7(
            $this->config['certPath'],
            $this->config['privateKeyPath'],
            $this->config['privateKeyPassword'],
            $this->config['tmpPath']
        ));
        $token = $openId->getToken('test');
        self::assertNotEmpty($token);
        self::assertSame($oid, $openId->getConfig()->getOid());
    }

}


================================================
FILE: tests/unit/OpenIdTest.php
================================================
<?php

namespace tests\unit;

use Codeception\Test\Unit;
use Esia\Config;
use Esia\Exceptions\AbstractEsiaException;
use Esia\Exceptions\InvalidConfigurationException;
use Esia\Http\GuzzleHttpClient;
use Esia\OpenId;
use Esia\Signer\Exceptions\SignFailException;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Client\ClientInterface;

class OpenIdTest extends Unit
{
    public $config;

    /**
     * @var OpenId
     */
    public $openId;

    /**
     * @throws InvalidConfigurationException
     */
    public function setUp(): void
    {
        $this->config = [
            'clientId' => 'INSP03211',
            'redirectUrl' => 'http://my-site.com/response.php',
            'portalUrl' => 'https://esia-portal1.test.gosuslugi.ru/',
            'privateKeyPath' => codecept_data_dir('server.key'),
            'privateKeyPassword' => 'test',
            'certPath' => codecept_data_dir('server.crt'),
            'tmpPath' => codecept_log_dir(),
        ];

        $config = new Config($this->config);

        $this->openId = new OpenId($config);
    }

    /**
     * @throws SignFailException
     * @throws AbstractEsiaException
     * @throws InvalidConfigurationException
     */
    public function testGetToken(): void
    {
        $config = new Config($this->config);

        $oid = '123';
        $oidBase64 = base64_encode('{ "urn:esia:sbj_id" : ' . $oid . '}');

        $client = $this->buildClientWithResponses([
            new Response(200, [], '{ "access_token": "test.' . $oidBase64 . '.test"}'),
        ]);
        $openId = new OpenId($config, $client);

        $token = $openId->getToken('test');
        self::assertNotEmpty($token);
        self::assertSame($oid, $openId->getConfig()->getOid());
    }

    /**
     * @throws InvalidConfigurationException
     * @throws AbstractEsiaException
     */
    public function testGetPersonInfo(): void
    {
        $config = new Config($this->config);
        $oid = '123';
        $config->setOid($oid);
        $config->setToken('test');

        $client = $this->buildClientWithResponses([
            new Response(200, [], '{"username": "test"}'),
        ]);
        $openId = new OpenId($config, $client);

        $info = $openId->getPersonInfo();
        self::assertNotEmpty($info);
        self::assertSame(['username' => 'test'], $info);
    }

    /**
     * @throws InvalidConfigurationException
     * @throws AbstractEsiaException
     */
    public function testGetContactInfo(): void
    {
        $config = new Config($this->config);
        $oid = '123';
        $config->setOid($oid);
        $config->setToken('test');

        $client = $this->buildClientWithResponses([
            new Response(200, [], '{"size": 2, "elements": ["phone", "email"]}'),
            new Response(200, [], '{"phone": "555 555 555"}'),
            new Response(200, [], '{"email": "test@gmail.com"}'),
        ]);
        $openId = new OpenId($config, $client);

        $info = $openId->getContactInfo();
        self::assertNotEmpty($info);
        self::assertSame([['phone' => '555 555 555'], ['email' => 'test@gmail.com']], $info);
    }

    /**
     * @throws InvalidConfigurationException
     * @throws AbstractEsiaException
     */
    public function testGetAddressInfo(): void
    {
        $config = new Config($this->config);
        $oid = '123';
        $config->setOid($oid);
        $config->setToken('test');

        $client = $this->buildClientWithResponses([
            new Response(200, [], '{"size": 2, "elements": ["phone", "email"]}'),
            new Response(200, [], '{"phone": "555 555 555"}'),
            new Response(200, [], '{"email": "test@gmail.com"}'),
        ]);
        $openId = new OpenId($config, $client);

        $info = $openId->getAddressInfo();
        self::assertNotEmpty($info);
        self::assertSame([['phone' => '555 555 555'], ['email' => 'test@gmail.com']], $info);
    }

    /**
     * @throws InvalidConfigurationException
     * @throws AbstractEsiaException
     */
    public function testGetDocInfo(): void
    {
        $config = new Config($this->config);
        $oid = '123';
        $config->setOid($oid);
        $config->setToken('test');

        $client = $this->buildClientWithResponses([
            new Response(200, [], '{"size": 2, "elements": ["phone", "email"]}'),
            new Response(200, [], '{"phone": "555 555 555"}'),
            new Response(200, [], '{"email": "test@gmail.com"}'),
        ]);
        $openId = new OpenId($config, $client);

        $info = $openId->getDocInfo();
        self::assertNotEmpty($info);
        self::assertSame([['phone' => '555 555 555'], ['email' => 'test@gmail.com']], $info);
    }

    /**
     * @throws InvalidConfigurationException
     */
    public function testBuildLogoutUrl(): void
    {
        $config = $this->openId->getConfig();

        $url = $config->getLogoutUrl() . '?client_id=' . $config->getClientId();
        $logoutUrl = $this->openId->buildLogoutUrl();
        self::assertSame($url, $logoutUrl);
    }

    /**
     * @throws InvalidConfigurationException
     */
    public function testBuildLogoutUrlWithRedirect(): void
    {
        $config = $this->openId->getConfig();

        $redirectUrl = 'test.example.com';
        $url = $config->getLogoutUrl() . '?client_id=' . $config->getClientId() . '&redirect_url=' . $redirectUrl;
        $logoutUrl = $this->openId->buildLogoutUrl($redirectUrl);
        self::assertSame($url, $logoutUrl);
    }

    /**
     * Client with prepared responses
     *
     * @param array $responses
     * @return ClientInterface
     */
    protected function buildClientWithResponses(array $responses): ClientInterface
    {
        $mock = new MockHandler($responses);

        $handler = HandlerStack::create($mock);
        $guzzleClient = new Client(['handler' => $handler]);

        return new GuzzleHttpClient($guzzleClient);
    }
}


================================================
FILE: tests/unit/Signer/SignerPKCS7Test.php
================================================
<?php

namespace tests\unit\Signer;

use Codeception\Test\Unit;
use Esia\Signer\Exceptions\CannotReadCertificateException;
use Esia\Signer\Exceptions\CannotReadPrivateKeyException;
use Esia\Signer\Exceptions\NoSuchCertificateFileException;
use Esia\Signer\Exceptions\NoSuchKeyFileException;
use Esia\Signer\Exceptions\NoSuchTmpDirException;
use Esia\Signer\Exceptions\SignFailException;
use Esia\Signer\SignerPKCS7;

/**
 * Class SignerPKCS7Test
 *
 * @coversDefaultClass \Esia\Signer\SignerPKCS7
 */
class SignerPKCS7Test extends Unit
{
    /**
     * @throws SignFailException
     */
    public function testSign(): void
    {
        $signer = new SignerPKCS7(
            codecept_data_dir('server.crt'),
            codecept_data_dir('server.key'),
            'test',
            codecept_log_dir()
        );

        $sign = $signer->sign('test');
        self::assertNotEmpty($sign);
    }

    /**
     * @throws SignFailException
     */
    public function testSignCertDoesNotExists(): void
    {
        $signer = new SignerPKCS7(
            '/test',
            codecept_data_dir('server.key'),
            'test',
            codecept_log_dir()
        );

        $this->expectException(NoSuchCertificateFileException::class);
        $signer->sign('test');
    }

    /**
     * @throws SignFailException
     */
    public function testPrivateKeyDoesNotExists(): void
    {
        $signer = new SignerPKCS7(
            codecept_data_dir('server.crt'),
            '/test',
            'test',
            codecept_log_dir()
        );

        $this->expectException(NoSuchKeyFileException::class);
        $signer->sign('test');
    }

    /**
     * @throws SignFailException
     */
    public function testTmpDirDoesNotExists(): void
    {
        $signer = new SignerPKCS7(
            codecept_data_dir('server.crt'),
            codecept_data_dir('server.key'),
            'test',
            '/'
        );

        $this->expectException(NoSuchTmpDirException::class);
        $signer->sign('test');
    }

    /**
     * @throws SignFailException
     */
    public function testTmpDirIsNotWritable(): void
    {
        $signer = new SignerPKCS7(
            codecept_data_dir('server.crt'),
            codecept_data_dir('server.key'),
            'test',
            codecept_log_dir('non_writable_directory')
        );

        $this->expectException(NoSuchTmpDirException::class);
        $signer->sign('test');
    }

    /**
     * @throws SignFailException
     */
    public function testCertificateIsNotReadable(): void
    {
        $signer = new SignerPKCS7(
            codecept_data_dir('non_readable_file'),
            codecept_data_dir('server.key'),
            'test',
            codecept_log_dir()
        );

        $this->expectException(CannotReadCertificateException::class);
        $signer->sign('test');
    }

    /**
     * @throws SignFailException
     */
    public function testPrivateKeyIsNotReadable(): void
    {
        $signer = new SignerPKCS7(
            codecept_data_dir('server.crt'),
            codecept_data_dir('non_readable_file'),
            'test',
            codecept_log_dir()
        );

        $this->expectException(CannotReadPrivateKeyException::class);
        $signer->sign('test');
    }
}


================================================
FILE: tests/unit/_bootstrap.php
================================================
<?php
// Here you can initialize variables that will be available to your tests


================================================
FILE: tests/unit.suite.yml
================================================
# Codeception Test Suite Configuration
#
# Suite for unit (internal) tests.

class_name: UnitTester
modules:
    enabled:
        - Asserts
        - \Helper\Unit
Download .txt
gitextract_ro8_8jf5/

├── .gitignore
├── .travis.yml
├── README.md
├── _config.yml
├── codeception.yml
├── composer.json
├── src/
│   └── Esia/
│       ├── Config.php
│       ├── Exceptions/
│       │   ├── AbstractEsiaException.php
│       │   ├── ForbiddenException.php
│       │   ├── InvalidConfigurationException.php
│       │   └── RequestFailException.php
│       ├── Http/
│       │   ├── Exceptions/
│       │   │   └── HttpException.php
│       │   └── GuzzleHttpClient.php
│       ├── OpenId.php
│       └── Signer/
│           ├── AbstractSignerPKCS7.php
│           ├── CliSignerPKCS7.php
│           ├── Exceptions/
│           │   ├── CannotGenerateRandomIntException.php
│           │   ├── CannotReadCertificateException.php
│           │   ├── CannotReadPrivateKeyException.php
│           │   ├── NoSuchCertificateFileException.php
│           │   ├── NoSuchKeyFileException.php
│           │   ├── NoSuchTmpDirException.php
│           │   └── SignFailException.php
│           ├── SignerInterface.php
│           └── SignerPKCS7.php
└── tests/
    ├── .configure-gost-openssl.sh
    ├── _bootstrap.php
    ├── _data/
    │   ├── server-gost.crt
    │   ├── server-gost.key
    │   ├── server.crt
    │   ├── server.csr
    │   └── server.key
    ├── _support/
    │   ├── Helper/
    │   │   └── Unit.php
    │   ├── UnitTester.php
    │   └── _generated/
    │       └── UnitTesterActions.php
    ├── unit/
    │   ├── ConfigTest.php
    │   ├── Http/
    │   │   └── GuzzleHttpClientTest.php
    │   ├── OpenIdCliOpensslTest.php
    │   ├── OpenIdTest.php
    │   ├── Signer/
    │   │   └── SignerPKCS7Test.php
    │   └── _bootstrap.php
    └── unit.suite.yml
Download .txt
SYMBOL INDEX (140 symbols across 27 files)

FILE: src/Esia/Config.php
  class Config (line 7) | class Config
    method __construct (line 45) | public function __construct(array $config = [])
    method getPortalUrl (line 85) | public function getPortalUrl(): string
    method getPrivateKeyPath (line 90) | public function getPrivateKeyPath(): string
    method getPrivateKeyPassword (line 95) | public function getPrivateKeyPassword(): string
    method getCertPath (line 100) | public function getCertPath(): string
    method getOid (line 105) | public function getOid(): string
    method setOid (line 110) | public function setOid(string $oid): void
    method getScope (line 115) | public function getScope(): array
    method getScopeString (line 120) | public function getScopeString(): string
    method getResponseType (line 125) | public function getResponseType(): string
    method getAccessType (line 130) | public function getAccessType(): string
    method getTmpPath (line 135) | public function getTmpPath(): string
    method getToken (line 140) | public function getToken(): ?string
    method setToken (line 145) | public function setToken(string $token): void
    method getClientId (line 150) | public function getClientId(): string
    method getRedirectUrl (line 155) | public function getRedirectUrl(): string
    method getTokenUrl (line 163) | public function getTokenUrl(): string
    method getCodeUrl (line 171) | public function getCodeUrl(): string
    method getPersonUrl (line 180) | public function getPersonUrl(): string
    method getLogoutUrl (line 191) | public function getLogoutUrl(): string

FILE: src/Esia/Exceptions/AbstractEsiaException.php
  class AbstractEsiaException (line 7) | abstract class AbstractEsiaException extends Exception

FILE: src/Esia/Exceptions/ForbiddenException.php
  class ForbiddenException (line 5) | class ForbiddenException extends AbstractEsiaException
    method getMessageForCode (line 7) | protected function getMessageForCode(int $code): string

FILE: src/Esia/Exceptions/InvalidConfigurationException.php
  class InvalidConfigurationException (line 5) | class InvalidConfigurationException extends AbstractEsiaException

FILE: src/Esia/Exceptions/RequestFailException.php
  class RequestFailException (line 5) | class RequestFailException extends AbstractEsiaException

FILE: src/Esia/Http/Exceptions/HttpException.php
  class HttpException (line 8) | class HttpException extends RuntimeException implements ClientExceptionI...

FILE: src/Esia/Http/GuzzleHttpClient.php
  class GuzzleHttpClient (line 13) | class GuzzleHttpClient implements ClientInterface
    method __construct (line 23) | public function __construct(Client $guzzle)
    method sendRequest (line 48) | public function sendRequest(RequestInterface $request): ResponseInterface

FILE: src/Esia/OpenId.php
  class OpenId (line 28) | class OpenId
    method __construct (line 51) | public function __construct(Config $config, ClientInterface $client = ...
    method setSigner (line 67) | public function setSigner(SignerInterface $signer): void
    method getConfig (line 75) | public function getConfig(): Config
    method buildUrl (line 90) | public function buildUrl()
    method buildLogoutUrl (line 122) | public function buildLogoutUrl(string $redirectUrl = null): string
    method getToken (line 144) | public function getToken(string $code): string
    method getPersonInfo (line 201) | public function getPersonInfo(): array
    method getContactInfo (line 217) | public function getContactInfo(): array
    method getAddressInfo (line 239) | public function getAddressInfo(): array
    method getDocInfo (line 260) | public function getDocInfo(): array
    method collectArrayElements (line 279) | private function collectArrayElements($elements): array
    method sendRequest (line 296) | private function sendRequest(RequestInterface $request): array
    method getTimeStamp (line 339) | private function getTimeStamp(): string
    method buildState (line 349) | private function buildState(): string
    method base64UrlSafeDecode (line 371) | private function base64UrlSafeDecode(string $string): string

FILE: src/Esia/Signer/AbstractSignerPKCS7.php
  class AbstractSignerPKCS7 (line 14) | abstract class AbstractSignerPKCS7
    method __construct (line 42) | public function __construct(
    method checkFilesExists (line 65) | protected function checkFilesExists(): void
    method getRandomString (line 90) | protected function getRandomString(): string
    method urlSafe (line 98) | protected function urlSafe(string $string): string

FILE: src/Esia/Signer/CliSignerPKCS7.php
  class CliSignerPKCS7 (line 7) | class CliSignerPKCS7 extends AbstractSignerPKCS7 implements SignerInterface
    method sign (line 12) | public function sign(string $message): string
    method run (line 47) | private function run(string $command): void

FILE: src/Esia/Signer/Exceptions/CannotGenerateRandomIntException.php
  class CannotGenerateRandomIntException (line 5) | class CannotGenerateRandomIntException extends SignFailException

FILE: src/Esia/Signer/Exceptions/CannotReadCertificateException.php
  class CannotReadCertificateException (line 5) | class CannotReadCertificateException extends SignFailException

FILE: src/Esia/Signer/Exceptions/CannotReadPrivateKeyException.php
  class CannotReadPrivateKeyException (line 5) | class CannotReadPrivateKeyException extends SignFailException

FILE: src/Esia/Signer/Exceptions/NoSuchCertificateFileException.php
  class NoSuchCertificateFileException (line 5) | class NoSuchCertificateFileException extends SignFailException

FILE: src/Esia/Signer/Exceptions/NoSuchKeyFileException.php
  class NoSuchKeyFileException (line 5) | class NoSuchKeyFileException extends SignFailException

FILE: src/Esia/Signer/Exceptions/NoSuchTmpDirException.php
  class NoSuchTmpDirException (line 5) | class NoSuchTmpDirException extends SignFailException

FILE: src/Esia/Signer/Exceptions/SignFailException.php
  class SignFailException (line 7) | class SignFailException extends AbstractEsiaException
    method getMessageForCode (line 9) | protected function getMessageForCode(int $code): string

FILE: src/Esia/Signer/SignerInterface.php
  type SignerInterface (line 7) | interface SignerInterface
    method sign (line 12) | public function sign(string $message): string;

FILE: src/Esia/Signer/SignerPKCS7.php
  class SignerPKCS7 (line 9) | class SignerPKCS7 extends AbstractSignerPKCS7 implements SignerInterface
    method addPKCS7Flag (line 13) | public function addPKCS7Flag(int $pkcs7Flag): void
    method sign (line 21) | public function sign(string $message): string

FILE: tests/_support/Helper/Unit.php
  class Unit (line 9) | class Unit extends Module

FILE: tests/_support/UnitTester.php
  class UnitTester (line 19) | class UnitTester extends \Codeception\Actor

FILE: tests/_support/_generated/UnitTesterActions.php
  type UnitTesterActions (line 12) | trait UnitTesterActions
    method getScenario (line 17) | abstract protected function getScenario();
    method assertEquals (line 45) | public function assertEquals($expected, $actual, $message = null, $del...
    method assertNotEquals (line 75) | public function assertNotEquals($expected, $actual, $message = null, $...
    method assertSame (line 90) | public function assertSame($expected, $actual, $message = null) {
    method assertNotSame (line 105) | public function assertNotSame($expected, $actual, $message = null) {
    method assertGreaterThan (line 120) | public function assertGreaterThan($expected, $actual, $message = null) {
    method assertGreaterThanOrEqual (line 135) | public function assertGreaterThanOrEqual($expected, $actual, $message ...
    method assertLessThan (line 150) | public function assertLessThan($expected, $actual, $message = null) {
    method assertLessThanOrEqual (line 165) | public function assertLessThanOrEqual($expected, $actual, $message = n...
    method assertContains (line 180) | public function assertContains($needle, $haystack, $message = null) {
    method assertNotContains (line 195) | public function assertNotContains($needle, $haystack, $message = null) {
    method assertRegExp (line 210) | public function assertRegExp($pattern, $string, $message = null) {
    method assertNotRegExp (line 225) | public function assertNotRegExp($pattern, $string, $message = null) {
    method assertStringStartsWith (line 240) | public function assertStringStartsWith($prefix, $string, $message = nu...
    method assertStringStartsNotWith (line 255) | public function assertStringStartsNotWith($prefix, $string, $message =...
    method assertEmpty (line 269) | public function assertEmpty($actual, $message = null) {
    method assertNotEmpty (line 283) | public function assertNotEmpty($actual, $message = null) {
    method assertNull (line 297) | public function assertNull($actual, $message = null) {
    method assertNotNull (line 311) | public function assertNotNull($actual, $message = null) {
    method assertTrue (line 325) | public function assertTrue($condition, $message = null)
    method assertNotTrue (line 340) | public function assertNotTrue($condition, $message = null)
    method assertFalse (line 355) | public function assertFalse($condition, $message = null)
    method assertNotFalse (line 370) | public function assertNotFalse($condition, $message = null)
    method assertFileExists (line 385) | public function assertFileExists($filename, $message = null)
    method assertFileNotExists (line 400) | public function assertFileNotExists($filename, $message = null) {
    method assertGreaterOrEquals (line 413) | public function assertGreaterOrEquals($expected, $actual, $description...
    method assertLessOrEquals (line 426) | public function assertLessOrEquals($expected, $actual, $description = ...
    method assertIsEmpty (line 438) | public function assertIsEmpty($actual, $description = null) {
    method assertArrayHasKey (line 451) | public function assertArrayHasKey($key, $actual, $description = null) {
    method assertArrayNotHasKey (line 464) | public function assertArrayNotHasKey($key, $actual, $description = nul...
    method assertArraySubset (line 480) | public function assertArraySubset($subset, $array, $strict = null, $me...
    method assertCount (line 493) | public function assertCount($expectedCount, $actual, $description = nu...
    method assertInstanceOf (line 506) | public function assertInstanceOf($class, $actual, $description = null) {
    method assertNotInstanceOf (line 519) | public function assertNotInstanceOf($class, $actual, $description = nu...
    method assertInternalType (line 532) | public function assertInternalType($type, $actual, $description = null) {
    method fail (line 545) | public function fail($message) {
    method expectException (line 581) | public function expectException($exception, $callback)
    method expectThrowable (line 616) | public function expectThrowable($throwable, $callback)

FILE: tests/unit/ConfigTest.php
  class ConfigTest (line 14) | class ConfigTest extends Unit
    method testGetScopeString (line 21) | public function testGetScopeString(): void
    method dataProviderForConstructor (line 39) | public function dataProviderForConstructor(): array
    method testConstruct (line 128) | public function testConstruct($config, string $expectedException = nul...
    method testGetTokenUrl (line 140) | public function testGetTokenUrl(): void
    method testGetCodeUrl (line 158) | public function testGetCodeUrl(): void
    method testGetPersonUrl (line 176) | public function testGetPersonUrl(): void
    method testGetPersonUrlWithoutOid (line 194) | public function testGetPersonUrlWithoutOid(): void
    method testGetLogoutUrl (line 212) | public function testGetLogoutUrl(): void

FILE: tests/unit/Http/GuzzleHttpClientTest.php
  class GuzzleHttpClientTest (line 21) | class GuzzleHttpClientTest extends Unit
    method testSendRequest (line 27) | public function testSendRequest(): void

FILE: tests/unit/OpenIdCliOpensslTest.php
  class OpenIdCliOpensslTest (line 12) | class OpenIdCliOpensslTest extends OpenIdTest
    method setUp (line 17) | public function setUp(): void
    method testGetToken (line 44) | public function testGetToken(): void

FILE: tests/unit/OpenIdTest.php
  class OpenIdTest (line 18) | class OpenIdTest extends Unit
    method setUp (line 30) | public function setUp(): void
    method testGetToken (line 52) | public function testGetToken(): void
    method testGetPersonInfo (line 73) | public function testGetPersonInfo(): void
    method testGetContactInfo (line 94) | public function testGetContactInfo(): void
    method testGetAddressInfo (line 117) | public function testGetAddressInfo(): void
    method testGetDocInfo (line 140) | public function testGetDocInfo(): void
    method testBuildLogoutUrl (line 162) | public function testBuildLogoutUrl(): void
    method testBuildLogoutUrlWithRedirect (line 174) | public function testBuildLogoutUrlWithRedirect(): void
    method buildClientWithResponses (line 190) | protected function buildClientWithResponses(array $responses): ClientI...

FILE: tests/unit/Signer/SignerPKCS7Test.php
  class SignerPKCS7Test (line 19) | class SignerPKCS7Test extends Unit
    method testSign (line 24) | public function testSign(): void
    method testSignCertDoesNotExists (line 40) | public function testSignCertDoesNotExists(): void
    method testPrivateKeyDoesNotExists (line 56) | public function testPrivateKeyDoesNotExists(): void
    method testTmpDirDoesNotExists (line 72) | public function testTmpDirDoesNotExists(): void
    method testTmpDirIsNotWritable (line 88) | public function testTmpDirIsNotWritable(): void
    method testCertificateIsNotReadable (line 104) | public function testCertificateIsNotReadable(): void
    method testPrivateKeyIsNotReadable (line 120) | public function testPrivateKeyIsNotReadable(): void
Condensed preview — 42 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (82K chars).
[
  {
    "path": ".gitignore",
    "chars": 80,
    "preview": ".idea\ntests/tmp/*\ntests/_output/*\npublic/*\nvendor\n\ntests/_data/non_readable_file"
  },
  {
    "path": ".travis.yml",
    "chars": 453,
    "preview": "dist: bionic\nlanguage: php\naddons:\n  apt:\n    packages:\n    - libengine-gost-openssl1.1\n\nbefore_install:\n    - sudo bash"
  },
  {
    "path": "README.md",
    "chars": 2810,
    "preview": "\n# Единая система идентификации и аутентификации (ЕСИА) OpenId \n\n[![Build Status](https://travis-ci.org/fr05t1k/esia.svg"
  },
  {
    "path": "_config.yml",
    "chars": 26,
    "preview": "theme: jekyll-theme-cayman"
  },
  {
    "path": "codeception.yml",
    "chars": 384,
    "preview": "actor: Tester\npaths:\n    tests: tests\n    log: tests/_output\n    data: tests/_data\n    support: tests/_support\n    envs:"
  },
  {
    "path": "composer.json",
    "chars": 676,
    "preview": "{\n  \"name\": \"fr05t1k/esia\",\n  \"license\": \"MIT\",\n  \"description\": \"OpenID ESIA authenticating\",\n  \"keywords\": [\n    \"esia"
  },
  {
    "path": "src/Esia/Config.php",
    "chars": 5003,
    "preview": "<?php\n\nnamespace Esia;\n\nuse Esia\\Exceptions\\InvalidConfigurationException;\n\nclass Config\n{\n    private $clientId;\n    pr"
  },
  {
    "path": "src/Esia/Exceptions/AbstractEsiaException.php",
    "chars": 110,
    "preview": "<?php\n\nnamespace Esia\\Exceptions;\n\nuse Exception;\n\nabstract class AbstractEsiaException extends Exception\n{\n}\n"
  },
  {
    "path": "src/Esia/Exceptions/ForbiddenException.php",
    "chars": 194,
    "preview": "<?php\n\nnamespace Esia\\Exceptions;\n\nclass ForbiddenException extends AbstractEsiaException\n{\n    protected function getMe"
  },
  {
    "path": "src/Esia/Exceptions/InvalidConfigurationException.php",
    "chars": 105,
    "preview": "<?php\n\nnamespace Esia\\Exceptions;\n\nclass InvalidConfigurationException extends AbstractEsiaException\n{\n}\n"
  },
  {
    "path": "src/Esia/Exceptions/RequestFailException.php",
    "chars": 96,
    "preview": "<?php\n\nnamespace Esia\\Exceptions;\n\nclass RequestFailException extends AbstractEsiaException\n{\n}\n"
  },
  {
    "path": "src/Esia/Http/Exceptions/HttpException.php",
    "chars": 194,
    "preview": "<?php\n\nnamespace Esia\\Http\\Exceptions;\n\nuse Psr\\Http\\Client\\ClientExceptionInterface;\nuse RuntimeException;\n\nclass HttpE"
  },
  {
    "path": "src/Esia/Http/GuzzleHttpClient.php",
    "chars": 1904,
    "preview": "<?php\n\nnamespace Esia\\Http;\n\nuse Esia\\Http\\Exceptions\\HttpException;\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\Exception\\Guz"
  },
  {
    "path": "src/Esia/OpenId.php",
    "chars": 10354,
    "preview": "<?php\n\nnamespace Esia;\n\nuse Esia\\Exceptions\\AbstractEsiaException;\nuse Esia\\Exceptions\\ForbiddenException;\nuse Esia\\Exce"
  },
  {
    "path": "src/Esia/Signer/AbstractSignerPKCS7.php",
    "chars": 2630,
    "preview": "<?php\n\nnamespace Esia\\Signer;\n\nuse Esia\\Signer\\Exceptions\\CannotReadCertificateException;\nuse Esia\\Signer\\Exceptions\\Can"
  },
  {
    "path": "src/Esia/Signer/CliSignerPKCS7.php",
    "chars": 2174,
    "preview": "<?php\n\nnamespace Esia\\Signer;\n\nuse Esia\\Signer\\Exceptions\\SignFailException;\n\nclass CliSignerPKCS7 extends AbstractSigne"
  },
  {
    "path": "src/Esia/Signer/Exceptions/CannotGenerateRandomIntException.php",
    "chars": 111,
    "preview": "<?php\n\nnamespace Esia\\Signer\\Exceptions;\n\nclass CannotGenerateRandomIntException extends SignFailException\n{\n}\n"
  },
  {
    "path": "src/Esia/Signer/Exceptions/CannotReadCertificateException.php",
    "chars": 109,
    "preview": "<?php\n\nnamespace Esia\\Signer\\Exceptions;\n\nclass CannotReadCertificateException extends SignFailException\n{\n}\n"
  },
  {
    "path": "src/Esia/Signer/Exceptions/CannotReadPrivateKeyException.php",
    "chars": 108,
    "preview": "<?php\n\nnamespace Esia\\Signer\\Exceptions;\n\nclass CannotReadPrivateKeyException extends SignFailException\n{\n}\n"
  },
  {
    "path": "src/Esia/Signer/Exceptions/NoSuchCertificateFileException.php",
    "chars": 109,
    "preview": "<?php\n\nnamespace Esia\\Signer\\Exceptions;\n\nclass NoSuchCertificateFileException extends SignFailException\n{\n}\n"
  },
  {
    "path": "src/Esia/Signer/Exceptions/NoSuchKeyFileException.php",
    "chars": 101,
    "preview": "<?php\n\nnamespace Esia\\Signer\\Exceptions;\n\nclass NoSuchKeyFileException extends SignFailException\n{\n}\n"
  },
  {
    "path": "src/Esia/Signer/Exceptions/NoSuchTmpDirException.php",
    "chars": 100,
    "preview": "<?php\n\nnamespace Esia\\Signer\\Exceptions;\n\nclass NoSuchTmpDirException extends SignFailException\n{\n}\n"
  },
  {
    "path": "src/Esia/Signer/Exceptions/SignFailException.php",
    "chars": 252,
    "preview": "<?php\n\nnamespace Esia\\Signer\\Exceptions;\n\nuse Esia\\Exceptions\\AbstractEsiaException;\n\nclass SignFailException extends Ab"
  },
  {
    "path": "src/Esia/Signer/SignerInterface.php",
    "chars": 208,
    "preview": "<?php\n\nnamespace Esia\\Signer;\n\nuse Esia\\Signer\\Exceptions\\SignFailException;\n\ninterface SignerInterface\n{\n    /**\n     *"
  },
  {
    "path": "src/Esia/Signer/SignerPKCS7.php",
    "chars": 2437,
    "preview": "<?php\n\nnamespace Esia\\Signer;\n\nuse Esia\\Signer\\Exceptions\\CannotReadCertificateException;\nuse Esia\\Signer\\Exceptions\\Can"
  },
  {
    "path": "tests/.configure-gost-openssl.sh",
    "chars": 353,
    "preview": "sed -i '1iopenssl_conf=openssl_def' /usr/lib/ssl/openssl.cnf\ntee -a /usr/lib/ssl/openssl.cnf <<EOF\n\n[openssl_def]\nengine"
  },
  {
    "path": "tests/_bootstrap.php",
    "chars": 100,
    "preview": "<?php\n// This is global bootstrap for autoloading\nrequire_once __DIR__ . '/../vendor/autoload.php';\n"
  },
  {
    "path": "tests/_data/server-gost.crt",
    "chars": 619,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIBnDCCAUmgAwIBAgIJAP+MfsgMHfr6MAoGBiqFAwICAwUAMCgxCzAJBgNVBAYT\nAlJVMRkwFwYDVQQDDBBUZXN0ZXI"
  },
  {
    "path": "tests/_data/server-gost.key",
    "chars": 148,
    "preview": "-----BEGIN PRIVATE KEY-----\nMEMCAQAwHAYGKoUDAgITMBIGByqFAwICIwEGByqFAwICHgEEIOjpBFHdMSI7NioS\nEjRMxGuikBUuBmvzIm+2JP9Z5yr"
  },
  {
    "path": "tests/_data/server.crt",
    "chars": 757,
    "preview": "-----BEGIN CERTIFICATE-----\nMIICATCCAWoCCQDuV6K/drn++DANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJS\nVTETMBEGA1UECBMKU29tZS1TdGF"
  },
  {
    "path": "tests/_data/server.csr",
    "chars": 603,
    "preview": "-----BEGIN CERTIFICATE REQUEST-----\nMIIBhDCB7gIBADBFMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEh\nMB8GA1UEChMYSW50ZXJ"
  },
  {
    "path": "tests/_data/server.key",
    "chars": 963,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,332C0B168FE46450\n\nT7FYR235atS5fngeCLDhNrTY"
  },
  {
    "path": "tests/_support/Helper/Unit.php",
    "chars": 215,
    "preview": "<?php\nnamespace Helper;\n// here you can define custom actions\n// all public methods declared in helper class will be ava"
  },
  {
    "path": "tests/_support/UnitTester.php",
    "chars": 598,
    "preview": "<?php\n\n\n/**\n * Inherited Methods\n * @method void wantToTest($text)\n * @method void wantTo($text)\n * @method void execute"
  },
  {
    "path": "tests/_support/_generated/UnitTesterActions.php",
    "chars": 19830,
    "preview": "<?php\n//[STAMP] c2c9446f63b7a0bf8b43e42b9b1b6f87\nnamespace _generated;\n\n// This class was automatically generated by bui"
  },
  {
    "path": "tests/unit/ConfigTest.php",
    "chars": 7053,
    "preview": "<?php\n\nnamespace tests\\unit;\n\nuse Codeception\\Test\\Unit;\nuse Esia\\Config;\nuse Esia\\Exceptions\\InvalidConfigurationExcept"
  },
  {
    "path": "tests/unit/Http/GuzzleHttpClientTest.php",
    "chars": 1212,
    "preview": "<?php\n\nnamespace tests\\unit\\Http;\n\nuse Codeception\\Test\\Unit;\nuse Esia\\Http\\GuzzleHttpClient;\nuse GuzzleHttp\\Client;\nuse"
  },
  {
    "path": "tests/unit/OpenIdCliOpensslTest.php",
    "chars": 2010,
    "preview": "<?php\n\nnamespace tests\\unit;\n\nuse Esia\\Config;\nuse Esia\\Exceptions\\AbstractEsiaException;\nuse Esia\\Exceptions\\InvalidCon"
  },
  {
    "path": "tests/unit/OpenIdTest.php",
    "chars": 6011,
    "preview": "<?php\n\nnamespace tests\\unit;\n\nuse Codeception\\Test\\Unit;\nuse Esia\\Config;\nuse Esia\\Exceptions\\AbstractEsiaException;\nuse"
  },
  {
    "path": "tests/unit/Signer/SignerPKCS7Test.php",
    "chars": 3289,
    "preview": "<?php\n\nnamespace tests\\unit\\Signer;\n\nuse Codeception\\Test\\Unit;\nuse Esia\\Signer\\Exceptions\\CannotReadCertificateExceptio"
  },
  {
    "path": "tests/unit/_bootstrap.php",
    "chars": 80,
    "preview": "<?php\n// Here you can initialize variables that will be available to your tests\n"
  },
  {
    "path": "tests/unit.suite.yml",
    "chars": 162,
    "preview": "# Codeception Test Suite Configuration\n#\n# Suite for unit (internal) tests.\n\nclass_name: UnitTester\nmodules:\n    enabled"
  }
]

About this extraction

This page contains the full source code of the fr05t1k/esia GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 42 files (73.0 KB), approximately 20.6k tokens, and a symbol index with 140 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!