Completed
Push — master ( 9cd38e...eb909a )
by Alberto
15s queued 12s
created

OAuth2Identifier::externalAuth()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 7
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 10
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
    /**
32
     * Configuration options
33
     *
34
     * - `fields` - Fields used in `/auth` endpoint using and external auth provider.
35
     * - `autoSignup` - flag indicating whether `/signup` should be invoked automatically in case of authentication failure.
36
     * - `signupRoles` - array of roles to use in `/signup` if `autoSignup` is set to `true`.
37
     * - `providers` - configured OAuth2 providers, see https://github.com/bedita/web-tools/wiki/OAuth2-providers-configurations
38
     *
39
     * @var array
40
     */
41
    protected $_defaultConfig = [
42
        'fields' => [
43
            'auth_provider' => 'auth_provider',
44
            'provider_username' => 'provider_username',
45
            'access_token' => 'access_token',
46
            'provider_userdata' => 'provider_userdata',
47
        ],
48
        'autoSignup' => false,
49
        'signupRoles' => [],
50
        'providers' => [], // configured OAuth2 providers
51
    ];
52
53
    /**
54
     * @inheritDoc
55
     */
56
    public function identify(array $credentials)
57
    {
58
        try {
59
            $result = $this->externalAuth($credentials);
60
        } catch (BEditaClientException $ex) {
61
            $this->log($ex->getMessage(), 'debug');
62
63
            if (!$this->getConfig('autoSignup') || $ex->getCode() !== 401) {
64
                return null;
65
            }
66
67
            return $this->signup($credentials);
68
        }
69
70
        return $result;
71
    }
72
73
    /**
74
     * Perform external login via `/auth`.
75
     *
76
     * @param array $credentials Identifier credentials
77
     * @return \ArrayObject
78
     */
79
    protected function externalAuth(array $credentials): ArrayObject
80
    {
81
        $apiClient = ApiClientProvider::getApiClient();
82
        $result = $apiClient->post('/auth', json_encode($credentials), ['Content-Type' => 'application/json']);
83
        $tokens = $result['meta'];
84
        $result = $apiClient->get('/auth/user', null, ['Authorization' => sprintf('Bearer %s', $tokens['jwt'])]);
85
86
        return new ArrayObject($result['data']
87
            + compact('tokens')
88
            + Hash::combine($result, 'included.{n}.attributes.name', 'included.{n}.id', 'included.{n}.type'));
89
    }
90
91
    /**
92
     * Perform OAuth2 signup and login after signup.
93
     *
94
     * @param array $credentials Identifier credentials
95
     * @return \ArrayObject|null;
96
     */
97
    protected function signup(array $credentials): ?ArrayObject
98
    {
99
        $data = $this->signupData($credentials);
100
        try {
101
            $apiClient = ApiClientProvider::getApiClient();
102
            $apiClient->setupTokens([]);
103
            $apiClient->post('/signup', json_encode($data), ['Content-Type' => 'application/json']);
104
            // login after signup
105
            $user = $this->externalAuth($credentials);
106
        } catch (BEditaClientException $ex) {
107
            $this->log($ex->getMessage(), 'warning');
108
            $this->log(json_encode($ex->getAttributes()), 'warning');
109
110
            return null;
111
        }
112
113
        return $user;
114
    }
115
116
    /**
117
     * Signup data from OAuth2 provider user data.
118
     *
119
     * @param array $credentials Identifier credentials
120
     * @return array
121
     */
122
    protected function signupData(array $credentials): array
123
    {
124
        $user = (array)$this->getConfig(sprintf('providers.%s.map', $credentials['auth_provider']));
125
        foreach ($user as $key => $value) {
126
            $user[$key] = Hash::get($credentials, sprintf('provider_userdata.%s', $value));
127
        }
128
        $roles = (array)$this->getConfig('signupRoles');
129
130
        return array_filter($user + $credentials + compact('roles'));
131
    }
132
}
133