Completed
Push — issue#666 ( 0f58f5...3afa36 )
by Guilherme
03:38
created

RemoteClaimFetcher::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 9
c 0
b 0
f 0
nc 1
nop 4
dl 0
loc 11
ccs 5
cts 5
cp 1
crap 1
rs 9.4285
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 LoginCidadao\OAuthBundle\Entity\ClientRepository;
16
use LoginCidadao\OAuthBundle\Model\ClientInterface;
17
use LoginCidadao\RemoteClaimsBundle\Entity\RemoteClaim;
18
use LoginCidadao\RemoteClaimsBundle\Entity\RemoteClaimRepository;
19
use LoginCidadao\RemoteClaimsBundle\Exception\ClaimProviderNotFoundException;
20
use LoginCidadao\RemoteClaimsBundle\Model\ClaimProviderInterface;
21
use LoginCidadao\RemoteClaimsBundle\Model\HttpUri;
22
use LoginCidadao\RemoteClaimsBundle\Model\RemoteClaimFetcherInterface;
23
use LoginCidadao\RemoteClaimsBundle\Model\RemoteClaimInterface;
24
use LoginCidadao\RemoteClaimsBundle\Model\TagUri;
25
use LoginCidadao\RemoteClaimsBundle\Parser\RemoteClaimParser;
26
use LoginCidadao\OAuthBundle\Entity\Client as ClaimProvider;
27
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
28
29
class RemoteClaimFetcher implements RemoteClaimFetcherInterface
30
{
31
    /** @var  Client */
32
    private $httpClient;
33
34
    /** @var RemoteClaimRepository */
35
    private $claimRepo;
36
37
    /** @var ClientRepository */
38
    private $clientRepo;
39
40
    /** @var EntityManagerInterface */
41
    private $em;
42
43
    /**
44
     * RemoteClaimFetcher constructor.
45
     * @param Client $httpClient
46
     * @param EntityManagerInterface $em
47
     * @param RemoteClaimRepository $claimRepository
48
     * @param ClientRepository $clientRepository
49 7
     */
50
    public function __construct(
51
        Client $httpClient,
52
        EntityManagerInterface $em,
53
        RemoteClaimRepository $claimRepository,
54
        ClientRepository $clientRepository
55 7
    ) {
56 7
        $this->em = $em;
57 7
        $this->httpClient = $httpClient;
58 7
        $this->claimRepo = $claimRepository;
59 7
        $this->clientRepo = $clientRepository;
60
    }
61 7
62
    public function fetchRemoteClaim($claimUri)
63
    {
64 7
        try {
65 3
            $uri = HttpUri::createFromString($claimUri);
66 3
        } catch (\Exception $e) {
67 3
            $claimName = TagUri::createFromString($claimUri);
68
            $uri = $this->discoverClaimUri($claimName);
69
        }
70 6
71 6
        $response = $this->httpClient->get($uri);
72
        $body = $response->getBody()->__toString();
73 6
74
        $remoteClaim = RemoteClaimParser::parseClaim($body, new RemoteClaim(), new ClaimProvider());
75 6
76
        return $remoteClaim;
77
    }
78
79
    /**
80
     * @param TagUri $claimName
81
     * @return string
82 3
     */
83
    public function discoverClaimUri(TagUri $claimName)
84 3
    {
85 3
        $uri = HttpUri::createFromComponents([
86 3
            'scheme' => 'https',
87 3
            'host' => $claimName->getAuthorityName(),
88 3
            'query' => http_build_query([
89
                'resource' => $claimName,
90
                'rel' => 'http://openid.net/specs/connect/1.0/claim',
91
            ]),
92 3
        ]);
93 3
94
        $response = $this->httpClient->get($uri->__toString());
95 3
        $json = json_decode($response->getBody());
96 3
97 3
        foreach ($json->links as $link) {
98 3
            if ($link->rel === 'http://openid.net/specs/connect/1.0/claim'
99
                && $json->subject === $claimName->__toString()) {
100
                return $link->href;
101
            }
102 1
        }
103
104
        throw new NotFoundHttpException("Couldn't find the Claim's URI");
105
    }
106
107
    /**
108
     * Fetches a RemoteClaimInterface via <code>fetchRemoteClaim</code>, persisting and returning the result.
109
     * @param TagUri|string $claimUri
110 2
     * @return RemoteClaimInterface
111
     */
112 2
    public function getRemoteClaim($claimUri)
113
    {
114 2
        $remoteClaim = $this->fetchRemoteClaim($claimUri);
115 2
116 2
        $existingClaim = $this->claimRepo->findOneBy(['name' => $remoteClaim->getName()]);
117 2
        if ($existingClaim instanceof RemoteClaimInterface) {
118
            $remoteClaim = $existingClaim;
119
            $newClaim = false;
120 2
        } else {
121 2
            $newClaim = true;
122 1
        }
123
124
        $provider = $this->getExistingClaimProvider($remoteClaim->getProvider());
125 2
        if ($provider instanceof ClaimProviderInterface) {
126 2
            $remoteClaim->setProvider($provider);
127
        }
128 2
129
        if ($newClaim) {
130
            $this->em->persist($remoteClaim);
131
            $this->em->flush();
132
        }
133
134
        return $remoteClaim;
135 2
    }
136
137 2
    /**
138
     * @param string[] $redirectUris
139 2
     * @return ClientInterface
140
     */
141
    private function findClaimProvider($redirectUris)
142
    {
143 2
        $clients = $this->clientRepo->findByRedirectUris($redirectUris);
144
145
        if (count($clients) > 1) {
146 2
            throw new \InvalidArgumentException('Ambiguous redirect_uris. More than one Relying Party found.');
147
        }
148 2
149
        return reset($clients);
150
    }
151
152 2
    /**
153 2
     * Gets the persisted/ORM-attached ClaimProvider
154
     * @param ClaimProviderInterface|null $provider
155
     * @return ClientInterface|null
156
     * @throws ClaimProviderNotFoundException
157 2
     */
158
    private function getExistingClaimProvider(ClaimProviderInterface $provider = null)
159
    {
160
        $existingProvider = null;
161
        if ($provider instanceof ClaimProviderInterface) {
162
            $existingProvider = $this->findClaimProvider($provider->getRedirectUris());
163
        }
164
165
        if ($existingProvider instanceof ClaimProviderInterface) {
166
            return $existingProvider;
167
        }
168
169
        // No pre-existing provider was found. Throw Exception!
170
        throw new ClaimProviderNotFoundException(
171
            "A Claim Provider was not found. This Identity Provider does NOT support Dynamic Claim Provider registration, so make sure it is already registered."
172
        );
173
    }
174
}
175