OAuthController   A
last analyzed

Complexity

Total Complexity 12

Size/Duplication

Total Lines 153
Duplicated Lines 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 61
c 7
b 0
f 0
dl 0
loc 153
rs 10
wmc 12

3 Methods

Rating   Name   Duplication   Size   Complexity  
A auth() 0 30 3
A __construct() 0 17 1
B verify() 0 50 8
1
<?php
2
3
namespace CodeCloud\Bundle\ShopifyBundle\Controller;
4
5
use CodeCloud\Bundle\ShopifyBundle\Event\PostAuthEvent;
6
use CodeCloud\Bundle\ShopifyBundle\Event\PreAuthEvent;
7
use CodeCloud\Bundle\ShopifyBundle\Exception\InsufficientScopeException;
8
use CodeCloud\Bundle\ShopifyBundle\Model\ShopifyStoreManagerInterface;
9
use CodeCloud\Bundle\ShopifyBundle\Security\HmacSignature;
10
use GuzzleHttp\ClientInterface;
11
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
12
use Symfony\Component\HttpFoundation\RedirectResponse;
13
use Symfony\Component\HttpFoundation\Request;
14
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
15
use Symfony\Component\OptionsResolver\OptionsResolver;
16
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
17
18
/**
19
 * Handles the OAuth handshake with Shopify.
20
 *
21
 * @see https://help.shopify.com/api/getting-started/authentication/oauth
22
 */
23
class OAuthController
24
{
25
    /**
26
     * @var UrlGeneratorInterface
27
     */
28
    private $router;
29
30
    /**
31
     * @var array
32
     */
33
    private $config;
34
35
    /**
36
     * @var ClientInterface
37
     */
38
    private $client;
39
40
    /**
41
     * @var ShopifyStoreManagerInterface
42
     */
43
    private $stores;
44
45
    /**
46
     * @var EventDispatcherInterface
47
     */
48
    private $dispatcher;
49
50
    /**
51
     * @var HmacSignature
52
     */
53
    private $hmacSignature;
54
55
    /**
56
     * @param UrlGeneratorInterface $router
57
     * @param array $config
58
     * @param ClientInterface $client
59
     * @param ShopifyStoreManagerInterface $stores
60
     * @param EventDispatcherInterface $dispatcher
61
     * @param HmacSignature $hmacSignature
62
     */
63
    public function __construct(
64
        UrlGeneratorInterface $router,
65
        array $config,
66
        ClientInterface $client,
67
        ShopifyStoreManagerInterface $stores,
68
        EventDispatcherInterface $dispatcher,
69
        HmacSignature $hmacSignature
70
    ) {
71
        $this->router = $router;
72
        $this->client = $client;
73
        $this->stores = $stores;
74
        $this->config = (new OptionsResolver())
75
            ->setRequired(['api_key', 'shared_secret', 'scope', 'redirect_route'])
76
            ->resolve($config)
77
        ;
78
        $this->dispatcher = $dispatcher;
79
        $this->hmacSignature = $hmacSignature;
80
    }
81
82
    /**
83
     * Handles initial auth request from Shopify.
84
     *
85
     * @param Request $request
86
     * @return RedirectResponse
87
     */
88
    public function auth(Request $request)
89
    {
90
        if (!$storeName = $request->get('shop')) {
91
            throw new BadRequestHttpException('Request is missing required parameter "shop".');
92
        }
93
94
        if ($response = $this->dispatcher->dispatch(
95
            PreAuthEvent::NAME,
96
            new PreAuthEvent($storeName))->getResponse()
0 ignored issues
show
Bug introduced by
The method getResponse() does not exist on Symfony\Component\EventDispatcher\Event. It seems like you code against a sub-type of Symfony\Component\EventDispatcher\Event such as CodeCloud\Bundle\ShopifyBundle\Event\PreAuthEvent or CodeCloud\Bundle\ShopifyBundle\Event\PostAuthEvent or Symfony\Component\HttpKe...ent\FilterResponseEvent or Symfony\Component\HttpKe...Event\PostResponseEvent or Symfony\Component\HttpKe...\Event\GetResponseEvent. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

96
            new PreAuthEvent($storeName))->/** @scrutinizer ignore-call */ getResponse()
Loading history...
97
        ) {
98
            return $response;
99
        }
100
101
        $verifyUrl = $this->router->generate('codecloud_shopify_verify', [], UrlGeneratorInterface::ABSOLUTE_URL);
102
        $verifyUrl = str_replace("http://", "https://", $verifyUrl);
103
        $nonce = uniqid();
104
105
        $this->stores->preAuthenticateStore($storeName, $nonce);
106
107
        $params = [
108
            'client_id'    => $this->config['api_key'],
109
            'scope'        => $this->config['scope'],
110
            'redirect_uri' => $verifyUrl,
111
            'state'        => $nonce,
112
        ];
113
114
        $shopifyEndpoint = 'https://%s/admin/oauth/authorize?%s';
115
        $url = sprintf($shopifyEndpoint, $storeName, http_build_query($params));
116
117
        return new RedirectResponse($url);
118
    }
119
120
    /**
121
     * Handles auth verification callback from Shopify.
122
     *
123
     * @param Request $request
124
     * @return string
125
     */
126
    public function verify(Request $request)
127
    {
128
        $authCode  = $request->get('code');
129
        $storeName = $request->get('shop');
130
        $nonce     = $request->get('state');
131
        $hmac      = $request->get('hmac');
132
133
        // todo validate store name
134
        // todo leverage options resolver?
135
136
        if (!$authCode || !$storeName || !$nonce || !$hmac) {
137
            throw new BadRequestHttpException('Request is missing one or more of required parameters: "code", "shop", "state", "hmac".');
138
        }
139
140
        if (!$this->hmacSignature->isValid($hmac, $request->query->all())) {
141
            throw new BadRequestHttpException('Invalid HMAC Signature');
142
        }
143
144
        $params = [
145
            'body' => \GuzzleHttp\json_encode([
146
                'client_id'     => $this->config['api_key'],
147
                'client_secret' => $this->config['shared_secret'],
148
                'code'          => $authCode
149
            ]),
150
            'headers' => [
151
                'Content-Type' => 'application/json',
152
                'Accept' => 'application/json',
153
            ],
154
        ];
155
156
        // todo this can fail - 400
157
        $response = $this->client->request('POST', 'https://' . $storeName . '/admin/oauth/access_token', $params);
158
        $responseJson = \GuzzleHttp\json_decode($response->getBody(), true);
159
160
        if ($responseJson['scope'] != $this->config['scope']) {
161
            throw new InsufficientScopeException($this->config['scope'], $responseJson['scope']);
162
        }
163
164
        $accessToken = $responseJson['access_token'];
165
        $this->stores->authenticateStore($storeName, $accessToken, $nonce);
166
167
        if ($response = $this->dispatcher->dispatch(
168
            PostAuthEvent::NAME,
169
            new PostAuthEvent($storeName, $accessToken))->getResponse()
170
        ) {
171
            return $response;
172
        }
173
174
        return new RedirectResponse(
175
            $this->router->generate($this->config['redirect_route'])
176
        );
177
    }
178
}
179