Project::loadUsers()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 6
rs 10
cc 1
nc 1
nop 1
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * BEdita, API-first content management framework
6
 * Copyright 2024 Atlas 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
16
namespace BEdita\ImportTools\Utility;
17
18
use Cake\Console\ConsoleIo;
19
use Cake\Database\Connection;
20
use Cake\Datasource\ConnectionManager;
21
use Cake\ORM\Locator\LocatorAwareTrait;
22
use Cake\Utility\Hash;
23
24
/**
25
 * Project utility
26
 */
27
class Project
28
{
29
    use LocatorAwareTrait;
30
31
    /**
32
     * @var \Cake\Database\Connection|null
33
     */
34
    protected $defaultConnection;
35
36
    /**
37
     * @var \Cake\Database\Connection|null
38
     */
39
    protected $importConnection;
40
41
    /**
42
     * @var \Cake\Console\ConsoleIo
43
     */
44
    protected $io;
45
46
    /**
47
     * @var \BEdita\Core\Model\Table\ApplicationsTable
48
     */
49
    protected $Applications;
50
51
    /**
52
     * @var \BEdita\Core\Model\Table\UsersTable
53
     */
54
    protected $Users;
55
56
    /**
57
     * Constructor
58
     *
59
     * @param \Cake\Console\ConsoleIo $io Console IO
60
     * @return void
61
     */
62
    public function __construct(ConsoleIo $io)
63
    {
64
        /** @var \BEdita\Core\Model\Table\ApplicationsTable $applications */
65
        $applications = $this->fetchTable('Applications');
66
        $this->Applications = $applications;
67
68
        /** @var \BEdita\Core\Model\Table\UsersTable $users */
69
        $users = $this->fetchTable('Users');
70
        $this->Users = $users;
71
72
        $this->io = $io;
73
74
        /** @var \Cake\Database\Connection|null $defaultConnection */
75
        $defaultConnection = ConnectionManager::get('default');
76
        $this->defaultConnection = $defaultConnection;
77
78
        /** @var \Cake\Database\Connection|null $importConnection */
79
        $importConnection = ConnectionManager::get('import');
80
        $this->importConnection = $importConnection;
81
    }
82
83
    /**
84
     * Check if `import` and `default` datasources are correctly configured
85
     *
86
     * @return bool
87
     */
88
    public function checkDatasourceConfig(): bool
89
    {
90
        if (!in_array('import', ConnectionManager::configured())) {
91
            $this->io->error('Unable to connect to `import` datasource, please review "Datasource" configuration');
92
93
            return false;
94
        }
95
        if (!$this->importConnection instanceof Connection || !$this->defaultConnection instanceof Connection) {
96
            $this->io->error('Wrong connection type, please review "Datasource" configuration');
97
98
            return false;
99
        }
100
101
        return true;
102
    }
103
104
    /**
105
     * Load applications on a given connection
106
     * Return an array having `name` as key,
107
     *
108
     * @param \Cake\Database\Connection $connection The Connection
109
     * @return array
110
     */
111
    public function loadApplications(Connection $connection): array
112
    {
113
        $this->Applications->setConnection($connection);
114
        $apps = $this->Applications->find()->select(['name', 'api_key', 'client_secret'])->toArray();
115
116
        return Hash::combine($apps, '{n}.name', '{n}');
117
    }
118
119
    /**
120
     * Load users on a given connection
121
     * Return an array having `username` as key,
122
     *
123
     * @param \Cake\Database\Connection $connection The Connection
124
     * @return array
125
     */
126
    public function loadUsers(Connection $connection): array
127
    {
128
        $this->Users->setConnection($connection);
129
        $users = $this->Users->find()->select(['username', 'password_hash'])->toArray();
130
131
        return Hash::combine($users, '{n}.username', '{n}');
132
    }
133
134
    /**
135
     * Update applications api keys using api keys provided in input array
136
     *
137
     * @param \Cake\Database\Connection $connection The connection
138
     * @param array $applications Application data
139
     * @return void
140
     */
141
    public function updateApplications(Connection $connection, array $applications): void
142
    {
143
        $this->Applications->setConnection($connection);
144
        foreach ($applications as $name => $application) {
145
            /** @var \BEdita\Core\Model\Entity\Application $entity */
146
            $entity = $this->Applications->find()->where(['name' => $name])->firstOrFail();
147
            $entity->api_key = $application->api_key;
148
            $entity->client_secret = $application->client_secret;
149
            $this->Applications->saveOrFail($entity);
150
        }
151
    }
152
153
    /**
154
     * Update users password hashes
155
     *
156
     * @param \Cake\Database\Connection $connection The connection
157
     * @param array $users Users data
158
     * @return void
159
     */
160
    public function updateUsers(Connection $connection, array $users): void
161
    {
162
        foreach ($users as $username => $user) {
163
            if (empty($user->password_hash)) {
164
                continue;
165
            }
166
            $query = sprintf(
167
                "UPDATE users SET password_hash = '%s' WHERE username = '%s'",
168
                $user->password_hash,
169
                $username
170
            );
171
            $connection->execute($query);
172
        }
173
    }
174
175
    /**
176
     * Review applications and update api keys
177
     *
178
     * @return bool
179
     */
180
    public function reviewApplications(): bool
181
    {
182
        $current = $this->loadApplications($this->defaultConnection);
183
        $import = $this->loadApplications($this->importConnection);
184
        $missing = array_diff(array_keys($import), array_keys($current));
185
        if (!empty($missing)) {
186
            $this->io->error(sprintf('Some applications are missing on current project: %s', implode(' ', $missing)));
187
188
            return false;
189
        }
190
        $update = array_intersect_key($current, $import);
191
        $this->updateApplications($this->importConnection, $update);
192
193
        return true;
194
    }
195
196
    /**
197
     * Review users and update password hashes
198
     *
199
     * @return bool
200
     */
201
    public function reviewUsers(): bool
202
    {
203
        $current = $this->loadUsers($this->defaultConnection);
204
        $import = $this->loadUsers($this->importConnection);
205
        $missing = array_diff(array_keys($import), array_keys($current));
206
        if (!empty($missing)) {
207
            $this->io->warning(sprintf('Some users are missing in current project [%d]', count($missing)));
208
            if ($this->io->askChoice('Do you want to proceed?', ['y', 'n'], 'n') === 'n') {
209
                $this->io->error('Aborting.');
210
211
                return false;
212
            }
213
        }
214
        $update = array_intersect_key($current, $import);
215
        $this->updateUsers($this->importConnection, $update);
216
217
        return true;
218
    }
219
}
220