Failed Conditions
Push — issue#702_rs ( ed72a1...cdafcf )
by Guilherme
07:33
created

RemoteClaimFetcher::getRemoteClaim()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 28
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 19
nc 8
nop 1
dl 0
loc 28
rs 8.5806
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
    public function __construct(
64
        Client $httpClient,
65
        EntityManagerInterface $em,
66
        RemoteClaimRepository $claimRepository,
67
        ClientManager $clientManager,
68
        EventDispatcherInterface $dispatcher
69
    ) {
70
        $this->em = $em;
71
        $this->httpClient = $httpClient;
72
        $this->claimRepo = $claimRepository;
73
        $this->clientManager = $clientManager;
74
        $this->dispatcher = $dispatcher;
75
    }
76
77
    public function fetchRemoteClaim($claimUri)
78
    {
79
        try {
80
            $uri = HttpUri::createFromString($claimUri);
81
        } catch (\Exception $e) {
82
            $claimName = TagUri::createFromString($claimUri);
83
            try {
84
                $uri = $this->discoverClaimUri($claimName);
85
            } catch (ClaimUriUnavailableException $e) {
86
                throw new NotFoundHttpException();
87
            }
88
        }
89
90
        try {
91
            $response = $this->httpClient->get($uri);
92
            $body = $response->getBody()->__toString();
93
94
            $remoteClaim = RemoteClaimParser::parseClaim($body, new RemoteClaim(), new ClaimProvider());
95
96
            return $remoteClaim;
97
        } catch (\Exception $e) {
98
            throw new NotFoundHttpException($e->getMessage(), $e);
99
        }
100
    }
101
102
    /**
103
     * @param TagUri|string $claimName
104
     * @return string
105
     */
106
    public function discoverClaimUri($claimName)
107
    {
108
        if (!$claimName instanceof TagUri) {
109
            $claimName = TagUri::createFromString($claimName);
110
        }
111
112
        $uri = $this->performDiscovery($claimName);
113
114
        if ($uri === false) {
115
            $uri = $this->discoveryFallback($claimName);
116
        } else {
117
            $event = new UpdateRemoteClaimUriEvent($claimName, $uri);
118
            $this->dispatcher->dispatch(RemoteClaimEvents::REMOTE_CLAIM_UPDATE_URI, $event);
119
        }
120
121
        return $uri;
122
    }
123
124
    /**
125
     * @param TagUri|string $claimName
126
     * @return mixed
127
     */
128
    private function performDiscovery(TagUri $claimName)
129
    {
130
        $uri = HttpUri::createFromComponents([
131
            'scheme' => 'https',
132
            'host' => $claimName->getAuthorityName(),
133
            'path' => '/.well-known/webfinger',
134
            'query' => http_build_query([
135
                'resource' => $claimName->__toString(),
136
                'rel' => 'http://openid.net/specs/connect/1.0/claim',
137
            ]),
138
        ]);
139
140
        try {
141
            $response = $this->httpClient->get($uri->__toString());
142
            $json = json_decode($response->getBody());
143
144
            if (property_exists($json, 'links')) {
145
                foreach ($json->links as $link) {
146
                    if ($link->rel === 'http://openid.net/specs/connect/1.0/claim'
147
                        && $json->subject === $claimName->__toString()) {
148
                        return $link->href;
149
                    }
150
                }
151
            }
152
        } catch (TransferException $e) {
153
            return false;
154
        }
155
156
        return false;
157
    }
158
159
    /**
160
     * @param $claimName
161
     * @return string
162
     */
163
    private function discoveryFallback(TagUri $claimName)
164
    {
165
        $remoteClaim = $this->getExistingRemoteClaim($claimName);
166
167
        if (!$remoteClaim instanceof RemoteClaimInterface || $remoteClaim->getUri() === null) {
168
            throw new ClaimUriUnavailableException();
169
        }
170
171
        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
    public function getRemoteClaim($claimUri)
181
    {
182
        $remoteClaim = $this->fetchRemoteClaim($claimUri);
183
184
        $existingClaim = $this->getExistingRemoteClaim($remoteClaim->getName());
185
        if ($existingClaim instanceof RemoteClaimInterface) {
186
            $existingClaim
187
                ->setDescription($remoteClaim->getDescription())
188
                ->setRecommendedScope($remoteClaim->getRecommendedScope())
189
                ->setEssentialScope($remoteClaim->getEssentialScope())
190
                ->setDisplayName($remoteClaim->getDisplayName());
191
            $remoteClaim = $existingClaim;
192
            $newClaim = false;
193
        } else {
194
            $newClaim = true;
195
        }
196
197
        $provider = $this->findClaimProvider($remoteClaim->getProvider()->getClientId());
198
        if ($provider instanceof ClaimProviderInterface) {
199
            $remoteClaim->setProvider($provider);
200
        }
201
202
        if ($newClaim) {
203
            $this->em->persist($remoteClaim);
204
        }
205
        $this->em->flush();
206
207
        return $remoteClaim;
208
    }
209
210
    /**
211
     * @param TagUri $claimName
212
     * @return RemoteClaimInterface|null
213
     */
214
    private function getExistingRemoteClaim(TagUri $claimName)
215
    {
216
        /** @var RemoteClaimInterface|null $remoteClaim */
217
        $remoteClaim = $this->claimRepo->findOneBy(['name' => $claimName]);
218
219
        return $remoteClaim;
220
    }
221
222
    /**
223
     * @param string $clientId
224
     * @return ClientInterface
225
     * @throws ClaimProviderNotFoundException
226
     */
227
    private function findClaimProvider($clientId)
228
    {
229
        $client = $this->clientManager->getClientById($clientId);
230
231
        if (!$client instanceof ClaimProviderInterface) {
232
            throw new ClaimProviderNotFoundException('Relying Party "'.$clientId.'" not found.');
233
        }
234
235
        return $client;
236
    }
237
}
238