Passed
Push — master ( 898cc7...450add )
by Thomas Mauro
03:07
created

DiscoveryMetadataProvider::discovery()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6.1308

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 14
nc 4
nop 1
dl 0
loc 24
ccs 11
cts 13
cp 0.8462
crap 6.1308
rs 9.2222
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace TMV\OpenIdClient\Provider;
6
7
use Http\Discovery\Psr17FactoryDiscovery;
8
use Http\Discovery\Psr18ClientDiscovery;
9
use Psr\Http\Client\ClientExceptionInterface;
10
use Psr\Http\Client\ClientInterface;
11
use Psr\Http\Message\RequestFactoryInterface;
12
use Psr\Http\Message\UriFactoryInterface;
13
use TMV\OpenIdClient\Exception\InvalidArgumentException;
14
use TMV\OpenIdClient\Exception\RuntimeException;
15
use function TMV\OpenIdClient\parse_metadata_response;
16
17
class DiscoveryMetadataProvider implements DiscoveryMetadataProviderInterface
18
{
19
    private const OIDC_DISCOVERY = '/.well-known/openid-configuration';
20
21
    private const OAUTH2_DISCOVERY = '/.well-known/oauth-authorization-server';
22
23
    private const WEBFINGER = '/.well-known/webfinger';
24
25
    private const REL = 'http://openid.net/specs/connect/1.0/issuer';
26
27
    private const AAD_MULTITENANT_DISCOVERY = 'https://login.microsoftonline.com/common/v2.0$' . self::OIDC_DISCOVERY;
28
29
    /** @var ClientInterface */
30
    private $client;
31
32
    /** @var RequestFactoryInterface */
33
    private $requestFactory;
34
35
    /** @var UriFactoryInterface */
36
    private $uriFactory;
37
38 2
    public function __construct(
39
        ?ClientInterface $client = null,
40
        ?RequestFactoryInterface $requestFactory = null,
41
        ?UriFactoryInterface $uriFactory = null
42
    ) {
43 2
        $this->client = $client ?: Psr18ClientDiscovery::find();
44 2
        $this->requestFactory = $requestFactory ?: Psr17FactoryDiscovery::findRequestFactory();
45 2
        $this->uriFactory = $uriFactory ?: Psr17FactoryDiscovery::findUrlFactory();
46 2
    }
47
48
    public function webfinger(string $resource): array
49
    {
50
        $uri = $this->uriFactory->createUri($resource);
51
        $webfingerUrl = $this->uriFactory->createUri('https://' . $uri->getHost() . ':' . $uri->getPort() . self::WEBFINGER)
52
            ->withQuery(\http_build_query(['resource' => (string) $uri, 'rel' => self::REL]));
53
54
        $request = $this->requestFactory->createRequest('GET', $webfingerUrl)
55
            ->withHeader('accept', 'application/json');
56
57
        try {
58
            $data = parse_metadata_response($this->client->sendRequest($request));
59
        } catch (ClientExceptionInterface $e) {
60
            throw new RuntimeException('Unable to fetch provider metadata', 0, $e);
61
        }
62
63
        $links = $data['links'] ?? [];
64
        $href = null;
65
        foreach ($links as $link) {
66
            if (! \is_array($link)) {
67
                continue;
68
            }
69
70
            if (($link['rel'] ?? null) !== self::REL) {
71
                continue;
72
            }
73
74
            if (! \array_key_exists('href', $link)) {
75
                continue;
76
            }
77
78
            $href = $link['href'];
79
        }
80
81
        if (! \is_string($href) || 0 !== \strpos($href, 'https://')) {
82
            throw new InvalidArgumentException('Invalid issuer location');
83
        }
84
85
        $metadata = $this->discovery($href);
86
87
        if ($metadata['issuer'] !== $href) {
88
            throw new RuntimeException('Discovered issuer mismatch');
89
        }
90
91
        return $metadata;
92
    }
93
94 2
    public function discovery(string $url): array
95
    {
96 2
        $uri = $this->uriFactory->createUri($url);
97 2
        $uriPath = $uri->getPath() ?: '/';
98
99 2
        if (false !== \strpos($uriPath, '/.well-known/')) {
100 1
            return $this->fetchOpenIdConfiguration((string) $uri);
101
        }
102
103
        $uris = [
104 1
            $uri->withPath(\rtrim($uriPath, '/') . self::OIDC_DISCOVERY),
105 1
            $uri->withPath('/' === $uriPath
106 1
                ? self::OAUTH2_DISCOVERY
107 1
                : self::OAUTH2_DISCOVERY . $uriPath),
108
        ];
109
110 1
        foreach ($uris as $wellKnownUri) {
111
            try {
112 1
                return $this->fetchOpenIdConfiguration((string) $wellKnownUri);
113
            } catch (RuntimeException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
114
            }
115
        }
116
117
        throw new RuntimeException('Unable to fetch provider metadata');
118
    }
119
120 2
    private function fetchOpenIdConfiguration(string $uri): array
121
    {
122 2
        $request = $this->requestFactory->createRequest('GET', $uri)
123 2
            ->withHeader('accept', 'application/json');
124
125
        try {
126 2
            $data = parse_metadata_response($this->client->sendRequest($request));
127
        } catch (ClientExceptionInterface $e) {
128
            throw new RuntimeException('Unable to fetch provider metadata', 0, $e);
129
        }
130
131 2
        if (! \array_key_exists('issuer', $data)) {
132
            throw new RuntimeException('Invalid metadata content, no "issuer" key found');
133
        }
134
135 2
        return $data;
136
    }
137
}
138