Passed
Push — issue#666 ( 0a0cb9...d66c4e )
by Guilherme
08:36
created

RemoteClaimFetcher::discoveryFallback()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
nop 1
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 3
rs 9.6666
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of the login-cidadao project or it's bundles.
4
 *
5
 * (c) Guilherme Donato <guilhermednt on github>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace LoginCidadao\RemoteClaimsBundle\Fetcher;
12
13
use Doctrine\ORM\EntityManagerInterface;
14
use GuzzleHttp\Client;
15
use GuzzleHttp\Exception\TransferException;
16
use LoginCidadao\LogBundle\Traits\LoggerAwareTrait;
17
use LoginCidadao\OAuthBundle\Entity\ClientRepository;
18
use LoginCidadao\OAuthBundle\Model\ClientInterface;
19
use LoginCidadao\OpenIDBundle\Manager\ClientManager;
20
use LoginCidadao\RemoteClaimsBundle\Entity\RemoteClaim;
21
use LoginCidadao\RemoteClaimsBundle\Entity\RemoteClaimRepository;
22
use LoginCidadao\RemoteClaimsBundle\Event\UpdateRemoteClaimUriEvent;
23
use LoginCidadao\RemoteClaimsBundle\Exception\ClaimProviderNotFoundException;
24
use LoginCidadao\RemoteClaimsBundle\Exception\ClaimUriUnavailableException;
25
use LoginCidadao\RemoteClaimsBundle\Model\ClaimProviderInterface;
26
use LoginCidadao\RemoteClaimsBundle\Model\HttpUri;
27
use LoginCidadao\RemoteClaimsBundle\Model\RemoteClaimFetcherInterface;
28
use LoginCidadao\RemoteClaimsBundle\Model\RemoteClaimInterface;
29
use LoginCidadao\RemoteClaimsBundle\Model\TagUri;
30
use LoginCidadao\RemoteClaimsBundle\Parser\RemoteClaimParser;
31
use LoginCidadao\OAuthBundle\Entity\Client as ClaimProvider;
32
use LoginCidadao\RemoteClaimsBundle\RemoteClaimEvents;
33
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
34
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
35
36
class RemoteClaimFetcher implements RemoteClaimFetcherInterface
37
{
38
    use LoggerAwareTrait;
39
40
    /** @var  Client */
41
    private $httpClient;
42
43
    /** @var RemoteClaimRepository */
44
    private $claimRepo;
45
46
    /** @var ClientManager */
47
    private $clientManager;
48
49
    /** @var EntityManagerInterface */
50
    private $em;
51
52
    /** @var EventDispatcherInterface */
53
    private $dispatcher;
54
55
    /**
56
     * RemoteClaimFetcher constructor.
57
     * @param Client $httpClient
58
     * @param EntityManagerInterface $em
59
     * @param RemoteClaimRepository $claimRepository
60
     * @param ClientManager $clientManager
61
     * @param EventDispatcherInterface $dispatcher
62
     */
63 11
    public function __construct(
64
        Client $httpClient,
65
        EntityManagerInterface $em,
66
        RemoteClaimRepository $claimRepository,
67
        ClientManager $clientManager,
68
        EventDispatcherInterface $dispatcher
69
    ) {
70 11
        $this->em = $em;
71 11
        $this->httpClient = $httpClient;
72 11
        $this->claimRepo = $claimRepository;
73 11
        $this->clientManager = $clientManager;
74 11
        $this->dispatcher = $dispatcher;
75 11
    }
76
77 8
    public function fetchRemoteClaim($claimUri)
78
    {
79
        try {
80 8
            $uri = HttpUri::createFromString($claimUri);
81 2
        } catch (\Exception $e) {
82 2
            $claimName = TagUri::createFromString($claimUri);
83
            try {
84 2
                $uri = $this->discoverClaimUri($claimName);
85 1
            } catch (ClaimUriUnavailableException $e) {
86 1
                throw new NotFoundHttpException();
87
            }
88
        }
89
90
        try {
91 7
            $response = $this->httpClient->get($uri);
92 6
            $body = $response->getBody()->__toString();
93
94 6
            $remoteClaim = RemoteClaimParser::parseClaim($body, new RemoteClaim(), new ClaimProvider());
95
96 6
            return $remoteClaim;
97 1
        } catch (\Exception $e) {
98 1
            throw new NotFoundHttpException();
99
        }
100
    }
101
102
    /**
103
     * @param TagUri|string $claimName
104
     * @return string
105
     */
106 4
    public function discoverClaimUri($claimName)
107
    {
108 4
        if (!$claimName instanceof TagUri) {
109 1
            $claimName = TagUri::createFromString($claimName);
110
        }
111
112 4
        $uri = $this->performDiscovery($claimName);
113
114 4
        if ($uri === false) {
0 ignored issues
show
introduced by
The condition $uri === false can never be false.
Loading history...
115 3
            $uri = $this->discoveryFallback($claimName);
116
        } else {
117 1
            $event = new UpdateRemoteClaimUriEvent($claimName, $uri);
118 1
            $this->dispatcher->dispatch(RemoteClaimEvents::REMOTE_CLAIM_UPDATE_URI, $event);
119
        }
120
121 2
        return $uri;
122
    }
123
124
    /**
125
     * @param TagUri|string $claimName
126
     * @return mixed
127
     */
128 4
    private function performDiscovery(TagUri $claimName)
129
    {
130 4
        $uri = HttpUri::createFromComponents([
131 4
            'scheme' => 'https',
132 4
            'host' => $claimName->getAuthorityName(),
133 4
            'path' => '/.well-known/webfinger',
134 4
            'query' => http_build_query([
135 4
                'resource' => $claimName->__toString(),
136 4
                'rel' => 'http://openid.net/specs/connect/1.0/claim',
137
            ]),
138
        ]);
139
140
        try {
141 4
            $response = $this->httpClient->get($uri->__toString());
142 2
            $json = json_decode($response->getBody());
143
144 2
            if (property_exists($json, 'links')) {
145 1
                foreach ($json->links as $link) {
146 1
                    if ($link->rel === 'http://openid.net/specs/connect/1.0/claim'
147 1
                        && $json->subject === $claimName->__toString()) {
148 2
                        return $link->href;
149
                    }
150
                }
151
            }
152 2
        } catch (TransferException $e) {
153 2
            return false;
154
        }
155
156 1
        return false;
157
    }
158
159
    /**
160
     * @param $claimName
161
     * @return string
162
     */
163 3
    private function discoveryFallback(TagUri $claimName)
164
    {
165 3
        $remoteClaim = $this->getExistingRemoteClaim($claimName);
166
167 3
        if (!$remoteClaim instanceof RemoteClaimInterface || $remoteClaim->getUri() === null) {
0 ignored issues
show
introduced by
The condition ! $remoteClaim instanceo...laim->getUri() === null can never be true.
Loading history...
168 2
            throw new ClaimUriUnavailableException();
169
        }
170
171 1
        return $remoteClaim->getUri();
172
    }
173
174
    /**
175
     * Fetches a RemoteClaimInterface via <code>fetchRemoteClaim</code>, persisting and returning the result.
176
     * @param TagUri|string $claimUri
177
     * @return RemoteClaimInterface
178
     * @throws ClaimProviderNotFoundException
179
     */
180 4
    public function getRemoteClaim($claimUri)
181
    {
182 4
        $remoteClaim = $this->fetchRemoteClaim($claimUri);
183
184 4
        $existingClaim = $this->getExistingRemoteClaim($remoteClaim->getName());
185 4
        if ($existingClaim instanceof RemoteClaimInterface) {
186
            $existingClaim
187 1
                ->setDescription($remoteClaim->getDescription())
188 1
                ->setRecommendedScope($remoteClaim->getRecommendedScope())
189 1
                ->setEssentialScope($remoteClaim->getEssentialScope())
190 1
                ->setDisplayName($remoteClaim->getDisplayName());
191 1
            $remoteClaim = $existingClaim;
192 1
            $newClaim = false;
193
        } else {
194 3
            $newClaim = true;
195
        }
196
197 4
        $provider = $this->findClaimProvider($remoteClaim->getProvider()->getClientId());
198 2
        if ($provider instanceof ClaimProviderInterface) {
199 2
            $remoteClaim->setProvider($provider);
200
        }
201
202 2
        if ($newClaim) {
203 1
            $this->em->persist($remoteClaim);
204
        }
205 2
        $this->em->flush();
206
207 2
        return $remoteClaim;
208
    }
209
210
    /**
211
     * @param TagUri $claimName
212
     * @return null|RemoteClaimInterface
213
     */
214 7
    private function getExistingRemoteClaim(TagUri $claimName)
215
    {
216
        /** @var RemoteClaimInterface $remoteClaim */
217 7
        $remoteClaim = $this->claimRepo->findOneBy(['name' => $claimName]);
218
219 7
        return $remoteClaim;
220
    }
221
222
    /**
223
     * @param string $clientId
224
     * @return ClientInterface
225
     * @throws ClaimProviderNotFoundException
226
     */
227 4
    private function findClaimProvider($clientId)
228
    {
229 4
        $client = $this->clientManager->getClientById($clientId);
230
231 4
        if (!$client instanceof ClaimProviderInterface) {
232 2
            throw new ClaimProviderNotFoundException('Relying Party "'.$clientId.'" not found.');
233
        }
234
235 2
        return $client;
236
    }
237
}
238