Passed
Pull Request — main (#12)
by Dante
01:09
created

Project::loadUsers()   A

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