Passed
Push — master ( 106006...c1ddb5 )
by Thomas Mauro
06:38 queued 11s
created

WebFingerProvider::fetch()   C

Complexity

Conditions 13
Paths 33

Size

Total Lines 63
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 14.3519

Importance

Changes 0
Metric Value
cc 13
eloc 35
nc 33
nop 1
dl 0
loc 63
ccs 28
cts 35
cp 0.8
crap 14.3519
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\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