Passed
Push — main ( d40f42...a0cbf5 )
by Breno
02:18
created

index.php$0 ➔ store()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 2
rs 10
1
<?php
2
declare(strict_types=1);
3
4
use BrenoRoosevelt\OAuth2\Client\GovBr;
5
use BrenoRoosevelt\OAuth2\Client\GovBrUser;
6
use Laminas\Diactoros\Response\JsonResponse;
7
use Laminas\Diactoros\Response\RedirectResponse;
8
use Laminas\Diactoros\ServerRequestFactory;
9
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
10
use League\OAuth2\Client\Grant\AuthorizationCode;
11
use Middlewares\Utils\Dispatcher;
12
use Middlewares\Whoops;
13
use Psr\Http\Message\ResponseInterface;
14
use Psr\Http\Message\ServerRequestInterface;
15
16
require __DIR__ . '/../vendor/autoload.php';
17
18
/**
19
 * ----------------------------------------------------------------------
20
 * Guia para rodar esse exemplo:
21
 * ----------------------------------------------------------------------
22
 * $ composer create project brenoroosevelt/oauth2-govbr [nova-pasta]
23
 * ----------------------------------------------------------------------
24
 * $ cd nova-pasta
25
 * ----------------------------------------------------------------------
26
 * Inclua em seu arquivo /etc/hosts a seguinte linha:
27
 *
28
 * 127.0.1.1       seu-dominio-cadastrado-no-gov-br.com.br
29
 * ----------------------------------------------------------------------
30
 * $ docker-compose up -d
31
 * ----------------------------------------------------------------------
32
 * Abra o browser (com https):
33
 *
34
 * https://seu-dominio-cadastrado-no-gov-br.com.br
35
 * ----------------------------------------------------------------------
36
 */
37
38
/**
39
 * Criamos um ajudante para armazenar o 'state' na sessão
40
 * Você pode armazernar no Redis, ou qualquer outro lugar
41
 * Mas não ignore a validação do state.
42
 */
43
$stateStorage = new class {
44
    public function __construct() {
45
        @session_start();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for session_start(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

45
        /** @scrutinizer ignore-unhandled */ @session_start();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
46
    }
47
48
    public function store($v): void {
49
        $_SESSION['oauth'] = $v;
50
    }
51
52
    public function has($v): bool {
53
        return isset($_SESSION['oauth']) && $_SESSION['oauth'] === $v;
54
    }
55
56
    public function clear(): void {
57
        unset($_SESSION['oauth']);
58
    }
59
};
60
61
/**
62
 * Criamos uma instância do Provider GovBr
63
 *
64
 * Observação:
65
 *     - Os parâmetros abaixos são sigilosos, evite comitar os valores no seu repositório
66
 *     - Prefira obter os valores abaixo usando getenv(...) ao invés de fixar no código
67
 */
68
$govBr =  new GovBr([
69
    // Client ID fornecido pelo GovBr
70
    'clientId'      => 'XXXXXXXX',
71
    // Senha fornecida pelo provedor GovBr
72
    'clientSecret'  => 'YYYYYYYY',
73
    // Url de redirecionamento cadastrada no GovBr
74
    'redirectUri'   => "https://seu-dominio-cadastrado-no-gov-br.com.br/seu-oauth-login"
75
]);
76
77
/**
78
 * Exemplo do fluxo Authorization Code.
79
 * O fluxo abaixo deve ser implementado dentro do controlador/classe responsável
80
 * pela rota informada na configuração acima ==> 'redirectUri'
81
 */
82
$authorizationCodeFlow =
83
    function(ServerRequestInterface $request) use ($govBr, $stateStorage) : ResponseInterface {
84
85
        $authorizationCode = $request->getQueryParams()['code'] ?? null;
86
        // Se não tivermos um código de autorização, vamos obter um
87
        if (empty($authorizationCode)) {
88
            $url = $govBr->getAuthorizationUrl();
89
            $stateStorage->store($govBr->getState());
90
            return new RedirectResponse($url); // redireciona o usuário para obter a autorização
91
        }
92
93
        $state = $request->getQueryParams()['state'] ?? null;
94
        // Possível ataque CSRF em andamento. Não ignore a validação do "state"
95
        if (empty($state) || !$stateStorage->has($state)) {
96
            $stateStorage->clear();
97
            return new JsonResponse(['error' => 'Invalid state'], 401);
98
        }
99
100
        try {
101
            // Tenta obter o access Access Token o Authorization Code
102
            $accessToken = $govBr->getAccessToken(new AuthorizationCode(), ['code' => $authorizationCode]);
103
        } catch (Throwable $e) {
104
            // Algo deu errado ao tentar obter o access token
105
            return new JsonResponse(['error' => $e->getMessage()], 401);
106
        }
107
108
        // Nesse ponto, já temos o Access Token... \o/
109
110
        // Opcional: Solicitar mais informações: dados do usuário
111
        /** @var GovBrUser $userGovBr */
112
        $userGovBr = $govBr->getResourceOwner($accessToken);
113
114
        // Opcional: Solicitar mais informações: foto/avatar
115
        // $avatar = $govBr->getAvatar($userGovBr);
116
117
        // Sua aplicação deve decidir o que fazer com os v e os dados obtidos
118
        // Considere a possibilidade de fazer cache do Access Token usando $accessToken->getExpires()
119
        return new JsonResponse($userGovBr->toArray());
120
    };
121
122
/**
123
 * Despachamos a requisição http
124
 * Seu framework certamente vai fazer isso aqui para você
125
 */
126
$request  = ServerRequestFactory::fromGlobals();
127
$response = Dispatcher::run([new Whoops(), $authorizationCodeFlow], $request);
128
(new SapiEmitter())->emit($response);
129