Passed
Push — master ( e62745...2f64c5 )
by Thomas Mauro
02:54
created

IssuerMetadataProvider::webfinger()   C

Complexity

Conditions 13
Paths 33

Size

Total Lines 59
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 0
Metric Value
cc 13
eloc 34
nc 33
nop 1
dl 0
loc 59
ccs 0
cts 34
cp 0
crap 182
rs 6.6166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\normalize_webfinger;
16
use function TMV\OpenIdClient\parse_metadata_response;
17
18
class IssuerMetadataProvider implements IssuerMetadataProviderInterface
19
{
20
    private const OIDC_DISCOVERY = '/.well-known/openid-configuration';
21
22
    private const OAUTH2_DISCOVERY = '/.well-known/oauth-authorization-server';
23
24
    private const WEBFINGER = '/.well-known/webfinger';
25
26
    private const REL = 'http://openid.net/specs/connect/1.0/issuer';
27
28
    private const AAD_MULTITENANT_DISCOVERY = 'https://login.microsoftonline.com/common/v2.0$' . self::OIDC_DISCOVERY;
29
30
    /** @var ClientInterface */
31
    private $client;
32
33
    /** @var RequestFactoryInterface */
34
    private $requestFactory;
35
36
    /** @var UriFactoryInterface */
37
    private $uriFactory;
38
39 2
    public function __construct(
40
        ?ClientInterface $client = null,
41
        ?RequestFactoryInterface $requestFactory = null,
42
        ?UriFactoryInterface $uriFactory = null
43
    ) {
44 2
        $this->client = $client ?: Psr18ClientDiscovery::find();
45 2
        $this->requestFactory = $requestFactory ?: Psr17FactoryDiscovery::findRequestFactory();
46 2
        $this->uriFactory = $uriFactory ?: Psr17FactoryDiscovery::findUrlFactory();
47 2
    }
48
49
    public function webfinger(string $resource): array
50
    {
51
        $resource = normalize_webfinger($resource);
52
        $parsedUrl = \parse_url(
53
            false !== \strpos($resource, '@')
54
                ? 'https://' . \explode('@', $resource)[1]
55
                : $resource
56
        );
57
58
        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...
59
            throw new RuntimeException('Unable to parse resource');
60
        }
61
62
        $host = $parsedUrl['host'];
63
        if ($port = ($parsedUrl['port'] ?? null)) {
64
            $host .= ':' . $port;
65
        }
66
67
        $webFingerUrl = $this->uriFactory->createUri('https://' . $host . self::WEBFINGER)
68
            ->withQuery(\http_build_query(['resource' => $resource, 'rel' => self::REL]));
69
70
        $request = $this->requestFactory->createRequest('GET', $webFingerUrl)
71
            ->withHeader('accept', 'application/json');
72
73
        try {
74
            $data = parse_metadata_response($this->client->sendRequest($request));
75
        } catch (ClientExceptionInterface $e) {
76
            throw new RuntimeException('Unable to fetch provider metadata', 0, $e);
77
        }
78
79
        $links = $data['links'] ?? [];
80
        $href = null;
81
        foreach ($links as $link) {
82
            if (! \is_array($link)) {
83
                continue;
84
            }
85
86
            if (($link['rel'] ?? null) !== self::REL) {
87
                continue;
88
            }
89
90
            if (! \array_key_exists('href', $link)) {
91
                continue;
92
            }
93
94
            $href = $link['href'];
95
        }
96
97
        if (! \is_string($href) || 0 !== \strpos($href, 'https://')) {
98
            throw new InvalidArgumentException('Invalid issuer location');
99
        }
100
101
        $metadata = $this->discovery($href);
102
103
        if ($metadata['issuer'] !== $href) {
104
            throw new RuntimeException('Discovered issuer mismatch');
105
        }
106
107
        return $metadata;
108
    }
109
110 2
    public function discovery(string $url): array
111
    {
112 2
        $uri = $this->uriFactory->createUri($url);
113 2
        $uriPath = $uri->getPath() ?: '/';
114
115 2
        if (false !== \strpos($uriPath, '/.well-known/')) {
116 1
            return $this->fetchOpenIdConfiguration((string) $uri);
117
        }
118
119
        $uris = [
120 1
            $uri->withPath(\rtrim($uriPath, '/') . self::OIDC_DISCOVERY),
121 1
            $uri->withPath('/' === $uriPath
122 1
                ? self::OAUTH2_DISCOVERY
123 1
                : self::OAUTH2_DISCOVERY . $uriPath),
124
        ];
125
126 1
        foreach ($uris as $wellKnownUri) {
127
            try {
128 1
                return $this->fetchOpenIdConfiguration((string) $wellKnownUri);
129
            } catch (RuntimeException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
130
            }
131
        }
132
133
        throw new RuntimeException('Unable to fetch provider metadata');
134
    }
135
136 2
    private function fetchOpenIdConfiguration(string $uri): array
137
    {
138 2
        $request = $this->requestFactory->createRequest('GET', $uri)
139 2
            ->withHeader('accept', 'application/json');
140
141
        try {
142 2
            $data = parse_metadata_response($this->client->sendRequest($request));
143
        } catch (ClientExceptionInterface $e) {
144
            throw new RuntimeException('Unable to fetch provider metadata', 0, $e);
145
        }
146
147 2
        if (! \array_key_exists('issuer', $data)) {
148
            throw new RuntimeException('Invalid metadata content, no "issuer" key found');
149
        }
150
151 2
        return $data;
152
    }
153
}
154