Passed
Pull Request — master (#66)
by Stefano
02:17
created

OAuth2Identifier::identify()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 8
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 15
rs 10
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * BEdita, API-first content management framework
6
 * Copyright 2022 ChannelWeb Srl, Chialab Srl
7
 *
8
 * This file is part of BEdita: you can redistribute it and/or modify
9
 * it under the terms of the GNU Lesser General Public License as published
10
 * by the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
14
 */
15
namespace BEdita\WebTools\Identifier;
16
17
use ArrayObject;
18
use Authentication\Identifier\AbstractIdentifier;
19
use BEdita\SDK\BEditaClientException;
20
use BEdita\WebTools\ApiClientProvider;
21
use Cake\Log\LogTrait;
22
use Cake\Utility\Hash;
23
24
/**
25
 * Identifies authentication credentials through an OAuth2 external provider.
26
 */
27
class OAuth2Identifier extends AbstractIdentifier
28
{
29
    use LogTrait;
30
31
    protected $_defaultConfig = [
32
        'fields' => [
33
            'auth_provider' => 'auth_provider',
34
            'provider_username' => 'provider_username',
35
            'access_token' => 'access_token',
36
            'provider_userdata' => 'provider_userdata',
37
        ],
38
        'autoSignup' => false,
39
        'signupRoles' => [],
40
        'providers' => [], // configured OAuth2 providers
41
    ];
42
43
    /**
44
     * @inheritDoc
45
     */
46
    public function identify(array $credentials)
47
    {
48
        try {
49
            $result = $this->externalAuth($credentials);
50
        } catch (BEditaClientException $ex) {
51
            $this->log($ex->getMessage(), 'debug');
52
53
            if (!$this->getConfig('autoSignup') || $ex->getCode() !== 401) {
54
                return null;
55
            }
56
57
            return $this->signup($credentials);
58
        }
59
60
        return $result;
61
    }
62
63
    /**
64
     * Perform external login via `/auth`.
65
     *
66
     * @param array $credentials Identifier credentials
67
     * @return \ArrayObject
68
     */
69
    protected function externalAuth(array $credentials): ArrayObject
70
    {
71
        $apiClient = ApiClientProvider::getApiClient();
72
        $result = $apiClient->post('/auth', json_encode($credentials), ['Content-Type' => 'application/json']);
73
        $tokens = $result['meta'];
74
        $result = $apiClient->get('/auth/user', null, ['Authorization' => sprintf('Bearer %s', $tokens['jwt'])]);
75
76
        return new ArrayObject($result['data']
77
            + compact('tokens')
78
            + Hash::combine($result, 'included.{n}.attributes.name', 'included.{n}.id', 'included.{n}.type'));
79
    }
80
81
    /**
82
     * Perform OAuth2 signup and login after signup.
83
     *
84
     * @param array $credentials Identifier credentials
85
     * @return \ArrayObject|null;
86
     */
87
    protected function signup(array $credentials): ?ArrayObject
88
    {
89
        $data = $this->signupData($credentials);
90
        try {
91
            $apiClient = ApiClientProvider::getApiClient();
92
            $apiClient->setupTokens([]);
93
            $apiClient->post('/signup', json_encode($data), ['Content-Type' => 'application/json']);
94
            // login after signup
95
            $user = $this->externalAuth($credentials);
96
        } catch (BEditaClientException $ex) {
97
            $this->log($ex->getMessage(), 'warning');
98
            $this->log(json_encode($ex->getAttributes()), 'warning');
99
100
            return null;
101
        }
102
103
        return $user;
104
    }
105
106
    /**
107
     * Signup data from OAuth2 provider user data.
108
     *
109
     * @param array $credentials Identifier credentials
110
     * @return array
111
     */
112
    protected function signupData(array $credentials): array
113
    {
114
        $user = (array)$this->getConfig(sprintf('providers.%s.map', $credentials['auth_provider']));
115
        foreach ($user as $key => $value) {
116
            $user[$key] = Hash::get($credentials, sprintf('provider_userdata.%s', $value));
117
        }
118
        $roles = (array)$this->getConfig('signupRoles');
119
120
        return array_filter($user + $credentials + compact('roles'));
121
    }
122
}
123