WebFingerProvider   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 101
Duplicated Lines 0 %

Test Coverage

Coverage 84.09%

Importance

Changes 0
Metric Value
eloc 50
dl 0
loc 101
ccs 37
cts 44
cp 0.8409
rs 10
c 0
b 0
f 0
wmc 18

2 Methods

Rating   Name   Duplication   Size   Complexity  
C fetch() 0 63 13
A __construct() 0 13 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace TMV\OpenIdClient\Issuer\Metadata\Provider;
6
7
use function array_key_exists;
8
use function explode;
9
use Http\Discovery\Psr17FactoryDiscovery;
10
use Http\Discovery\Psr18ClientDiscovery;
11
use function http_build_query;
12
use function is_array;
13
use function is_string;
14
use function parse_url;
15
use Psr\Http\Client\ClientExceptionInterface;
16
use Psr\Http\Client\ClientInterface;
17
use Psr\Http\Message\RequestFactoryInterface;
18
use Psr\Http\Message\UriFactoryInterface;
19
use function strpos;
20
use TMV\OpenIdClient\Exception\InvalidArgumentException;
21
use TMV\OpenIdClient\Exception\RuntimeException;
22
use function TMV\OpenIdClient\normalize_webfinger;
23
use function TMV\OpenIdClient\parse_metadata_response;
24
25
final class WebFingerProvider implements WebFingerProviderInterface
26
{
27
    private const OIDC_DISCOVERY = '/.well-known/openid-configuration';
28
29
    private const WEBFINGER = '/.well-known/webfinger';
30
31
    private const REL = 'http://openid.net/specs/connect/1.0/issuer';
32
33
    private const AAD_MULTITENANT_DISCOVERY = 'https://login.microsoftonline.com/common/v2.0$' . self::OIDC_DISCOVERY;
34
35
    /** @var ClientInterface */
36
    private $client;
37
38
    /** @var RequestFactoryInterface */
39
    private $requestFactory;
40
41
    /** @var UriFactoryInterface */
42
    private $uriFactory;
43
44
    /** @var DiscoveryProviderInterface */
45
    private $discoveryProvider;
46
47 2
    public function __construct(
48
        ?ClientInterface $client = null,
49
        ?RequestFactoryInterface $requestFactory = null,
50
        ?UriFactoryInterface $uriFactory = null,
51
        ?DiscoveryProviderInterface $discoveryProvider = null
52
    ) {
53 2
        $this->client = $client ?: Psr18ClientDiscovery::find();
54 2
        $this->requestFactory = $requestFactory ?: Psr17FactoryDiscovery::findRequestFactory();
55 2
        $this->uriFactory = $uriFactory ?: Psr17FactoryDiscovery::findUrlFactory();
56 2
        $this->discoveryProvider = $discoveryProvider ?: new DiscoveryProvider(
57 1
            $this->client,
58 1
            $this->requestFactory,
59 1
            $this->uriFactory
60
        );
61 2
    }
62
63 1
    public function fetch(string $resource): array
64
    {
65 1
        $resource = normalize_webfinger($resource);
66 1
        $parsedUrl = parse_url(
67 1
            false !== strpos($resource, '@')
68 1
                ? 'https://' . explode('@', $resource)[1]
69 1
                : $resource
70
        );
71
72 1
        if (! is_array($parsedUrl) || ! array_key_exists('host', $parsedUrl)) {
0 ignored issues
show
introduced by
The condition is_array($parsedUrl) is always true.
Loading history...
73
            throw new RuntimeException('Unable to parse resource');
74
        }
75
76 1
        $host = $parsedUrl['host'];
77
78
        /** @var string|int|null $port */
79 1
        $port = $parsedUrl['port'] ?? null;
80
81 1
        if ((int) $port > 0) {
82
            $host .= ':' . $port;
83
        }
84
85 1
        $webFingerUrl = $this->uriFactory->createUri('https://' . $host . self::WEBFINGER)
86 1
            ->withQuery(http_build_query(['resource' => $resource, 'rel' => self::REL]));
87
88 1
        $request = $this->requestFactory->createRequest('GET', $webFingerUrl)
89 1
            ->withHeader('accept', 'application/json');
90
91
        try {
92 1
            $data = parse_metadata_response($this->client->sendRequest($request));
93
        } catch (ClientExceptionInterface $e) {
94
            throw new RuntimeException('Unable to fetch provider metadata', 0, $e);
95
        }
96
97 1
        $links = $data['links'] ?? [];
98 1
        $href = null;
99 1
        foreach ($links as $link) {
100 1
            if (! is_array($link)) {
101
                continue;
102
            }
103
104 1
            if (($link['rel'] ?? null) !== self::REL) {
105 1
                continue;
106
            }
107
108 1
            if (! array_key_exists('href', $link)) {
109 1
                continue;
110
            }
111
112 1
            $href = $link['href'];
113
        }
114
115 1
        if (! is_string($href) || 0 !== strpos($href, 'https://')) {
116
            throw new InvalidArgumentException('Invalid issuer location');
117
        }
118
119 1
        $metadata = $this->discoveryProvider->discovery($href);
120
121 1
        if (($metadata['issuer'] ?? null) !== $href) {
122
            throw new RuntimeException('Discovered issuer mismatch');
123
        }
124
125 1
        return $metadata;
126
    }
127
}
128