Completed
Push — main ( 8e589f...1880fc )
by Stefano
01:17 queued 24s
created

Project::reviewApplications()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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