Completed
Branch dev (bc6e47)
by Raffael
02:23
created

Server::addGroup()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 32
rs 8.439
c 0
b 0
f 0
cc 5
eloc 18
nc 5
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Balloon
7
 *
8
 * @author      Raffael Sahli <[email protected]>
9
 * @copyright   Copryright (c) 2012-2017 gyselroth GmbH (https://gyselroth.com)
10
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
11
 */
12
13
namespace Balloon;
14
15
use Balloon\Filesystem\Acl;
16
use Balloon\Filesystem\Storage;
17
use Balloon\Server\Exception;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Balloon\Exception.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
18
use Balloon\Server\Group;
19
use Balloon\Server\Group\Exception as GroupException;
20
use Balloon\Server\User;
21
use Balloon\Server\User\Exception as UserException;
22
use Generator;
23
use Micro\Auth\Identity;
24
use MongoDB\BSON\ObjectId;
25
use MongoDB\BSON\UTCDateTime;
26
use MongoDB\Database;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Balloon\Database.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
27
use Psr\Log\LoggerInterface;
28
29
class Server
30
{
31
    /**
32
     * Database.
33
     *
34
     * @var Database
35
     */
36
    protected $db;
37
38
    /**
39
     * Storage.
40
     *
41
     * @var Storage
42
     */
43
    protected $storage;
44
45
    /**
46
     * LoggerInterface.
47
     *
48
     * @var LoggerInterface
49
     */
50
    protected $logger;
51
52
    /**
53
     * Hook.
54
     *
55
     * @var Hook
56
     */
57
    protected $hook;
58
59
    /**
60
     * Authenticated identity.
61
     *
62
     * @var User
63
     */
64
    protected $identity;
65
66
    /**
67
     * Acl.
68
     *
69
     * @var Acl
70
     */
71
    protected $acl;
72
73
    /**
74
     * Temporary store.
75
     *
76
     * @var string
77
     */
78
    protected $temp_dir = '/tmp/balloon';
79
80
    /**
81
     * Max file version.
82
     *
83
     * @var int
84
     */
85
    protected $max_file_version = 8;
86
87
    /**
88
     * Max file size.
89
     *
90
     * @var int
91
     */
92
    protected $max_file_size = 1073741824;
93
94
    /**
95
     * Initialize.
96
     *
97
     * @param Database        $db
98
     * @param Storage         $storage
99
     * @param LoggerInterface $logger
100
     * @param Hook            $hook
101
     * @param Acl             $acl
102
     * @param iterable        $config
103
     */
104
    public function __construct(Database $db, Storage $storage, LoggerInterface $logger, Hook $hook, Acl $acl, ?Iterable $config = null)
105
    {
106
        $this->db = $db;
107
        $this->storage = $storage;
108
        $this->logger = $logger;
109
        $this->hook = $hook;
110
        $this->acl = $acl;
111
112
        $this->setOptions($config);
113
    }
114
115
    /**
116
     * Set options.
117
     *
118
     * @param iterable $config
119
     *
120
     * @return Server
121
     */
122
    public function setOptions(?Iterable $config = null): self
123
    {
124
        if (null === $config) {
125
            return $this;
126
        }
127
128
        foreach ($config as $name => $value) {
129
            switch ($name) {
130
                case 'temp_dir':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
131
                    $this->temp_dir = (string) $value;
132
133
                break;
134
                case 'max_file_version':
135
                case 'max_file_size':
136
                    $this->{$name} = (int) $value;
137
138
                break;
139
                default:
140
                    throw new Exception('invalid option '.$name.' given');
141
            }
142
        }
143
144
        return $this;
145
    }
146
147
    /**
148
     * Get temporary directory.
149
     *
150
     * @return string
151
     */
152
    public function getTempDir(): string
153
    {
154
        return $this->temp_dir;
155
    }
156
157
    /**
158
     * Get max file version.
159
     *
160
     * @return int
161
     */
162
    public function getMaxFileVersion(): int
163
    {
164
        return $this->max_file_version;
165
    }
166
167
    /**
168
     * Get max file size.
169
     *
170
     * @return int
171
     */
172
    public function getMaxFileSize(): int
173
    {
174
        return $this->max_file_size;
175
    }
176
177
    /**
178
     * Filesystem factory.
179
     *
180
     * @return Filesystem
181
     */
182
    public function getFilesystem(?User $user = null): Filesystem
183
    {
184
        if (null !== $user) {
185
            return new Filesystem($this, $this->db, $this->hook, $this->logger, $this->storage, $this->acl, $user);
186
        }
187
        if ($this->identity instanceof User) {
188
            return new Filesystem($this, $this->db, $this->hook, $this->logger, $this->storage, $this->acl, $this->identity);
189
        }
190
191
        return new Filesystem($this, $this->db, $this->hook, $this->logger, $this->storage, $this->acl);
192
    }
193
194
    /**
195
     * Add user.
196
     *
197
     * @param string $username
198
     * @param string $password
199
     * @param array  $attributes
200
     *
201
     * @return ObjectId
202
     */
203
    public function addUser(string $username, ?string $password = null, array $attributes = []): ObjectId
204
    {
205
        if ($this->userExists($username)) {
206
            throw new UserException('user does already exists', UserException::ALREADY_EXISTS);
207
        }
208
209
        $defaults = [
210
            'deleted' => false,
211
        ];
212
213
        $attributes = array_merge($defaults, $attributes);
214
        $attributes['created'] = new UTCDateTime();
215
        $attributes['username'] = $username;
216
217
        if (null !== $password) {
218
            $attributes['password'] = password_hash($password, PASSWORD_DEFAULT);
219
        }
220
221
        $result = $this->db->user->insertOne($attributes);
222
223
        return $result->getInsertedId();
224
    }
225
226
    /**
227
     * Check if user exists.
228
     *
229
     * @return bool
230
     */
231
    public function userExists(string $username): bool
232
    {
233
        return  1 === $this->db->user->count(['username' => $username]);
234
    }
235
236
    /**
237
     * Check if group exists.
238
     *
239
     * @return bool
240
     */
241
    public function groupExists(string $name): bool
242
    {
243
        return  1 === $this->db->group->count(['name' => $name]);
244
    }
245
246
    /**
247
     * Get user by id.
248
     *
249
     * @param ObjectId $id
250
     *
251
     * @return User
252
     */
253
    public function getUserById(ObjectId $id): User
254
    {
255
        $attributes = $this->db->user->findOne([
256
           '_id' => $id,
257
        ]);
258
259
        if (null === $attributes) {
260
            throw new UserException('user does not exists', UserException::DOES_NOT_EXISTS);
261
        }
262
263
        return new User($attributes, $this, $this->db, $this->logger);
264
    }
265
266
    /**
267
     * Get users by id.
268
     *
269
     * @param array $id
270
     *
271
     * @return Generator
272
     */
273
    public function getUsersById(array $id): Generator
274
    {
275
        $find = [];
276
        foreach ($id as $i) {
277
            $find[] = new ObjectId($i);
278
        }
279
280
        $filter = [
281
            '_id' => ['$in' => $find],
282
        ];
283
284
        $users = $this->db->user->find($filter);
285
286
        foreach ($users as $attributes) {
287
            yield new User($attributes, $this, $this->db, $this->logger);
288
        }
289
    }
290
291
    /**
292
     * Set Identity.
293
     *
294
     * @param Identity $identity
295
     *
296
     * @return bool
297
     */
298
    public function setIdentity(Identity $identity): bool
299
    {
300
        $result = $this->db->user->findOne(['username' => $identity->getIdentifier()]);
301
        $this->hook->run('preServerIdentity', [$identity, &$result]);
302
303
        if (null === $result) {
304
            throw new Exception\NotAuthenticated('user does not exists', Exception\NotAuthenticated::USER_DOES_NOT_EXISTS);
305
        }
306
307
        if (isset($result['deleted']) && true === $result['deleted']) {
308
            throw new Exception\NotAuthenticated(
309
                'user is disabled and can not be used',
310
                Exception\NotAuthenticated::USER_DELETED
311
            );
312
        }
313
314
        $user = new User($result, $this, $this->db, $this->logger);
315
        $this->identity = $user;
316
        $user->updateIdentity($identity);
317
        $this->hook->run('postServerIdentity', [$this, $user]);
318
319
        return true;
320
    }
321
322
    /**
323
     * Get authenticated user.
324
     *
325
     * @return User
326
     */
327
    public function getIdentity(): ?User
328
    {
329
        return $this->identity;
330
    }
331
332
    /**
333
     * Get user by name.
334
     *
335
     * @param string $name
336
     *
337
     * @return User
338
     */
339
    public function getUserByName(string $name): User
340
    {
341
        $attributes = $this->db->user->findOne([
342
           'username' => $name,
343
        ]);
344
345
        if (null === $attributes) {
346
            throw new UserException('user does not exists', UserException::DOES_NOT_EXISTS);
347
        }
348
349
        return new User($attributes, $this, $this->db, $this->logger);
350
    }
351
352
    /**
353
     * Get users.
354
     *
355
     * @param array $filter
356
     *
357
     * @return Generator
358
     */
359
    public function getUsers(array $filter): Generator
360
    {
361
        $users = $this->db->user->find($filter);
362
363
        foreach ($users as $attributes) {
364
            yield new User($attributes, $this, $this->db, $this->logger);
365
        }
366
    }
367
368
    /**
369
     * Get groups.
370
     *
371
     * @param array $filter
372
     *
373
     * @return Generator
374
     */
375
    public function getGroups(array $filter): Generator
376
    {
377
        $groups = $this->db->group->find($filter);
378
379
        foreach ($groups as $attributes) {
380
            yield new Group($attributes, $this, $this->db, $this->logger);
381
        }
382
    }
383
384
    /**
385
     * Get group by name.
386
     *
387
     * @param string $name
388
     *
389
     * @return Group
390
     */
391
    public function getGroupByName(string $name): Group
392
    {
393
        $attributes = $this->db->group->findOne([
394
           'username' => $name,
395
        ]);
396
397
        if (null === $attributes) {
398
            throw new GroupException('group does not exists', GroupException::DOES_NOT_EXISTS);
399
        }
400
401
        return new Group($attributes, $this, $this->db, $this->logger);
402
    }
403
404
    /**
405
     * Get group by id.
406
     *
407
     * @param string $id
408
     *
409
     * @return User
410
     */
411
    public function getGroupById(ObjectId $id): Group
412
    {
413
        $attributes = $this->db->group->findOne([
414
           '_id' => $id,
415
        ]);
416
417
        if (null === $attributes) {
418
            throw new GroupException('group does not exists', GroupException::DOES_NOT_EXISTS);
419
        }
420
421
        return new Group($attributes, $this, $this->db, $this->logger);
422
    }
423
424
    /**
425
     * Add group.
426
     *
427
     * @param string $name
428
     * @param array  $member
429
     * @param array  $attributes
430
     *
431
     * @return ObjectId
432
     */
433
    public function addGroup(string $name, array $member, array $attributes = []): ObjectId
434
    {
435
        if ($this->groupExists($name)) {
436
            throw new GroupException('group does already exists', GroupException::ALREADY_EXISTS);
437
        }
438
439
        $defaults = [
440
            'name' => $name,
441
            'created' => new UTCDateTime(),
442
            'deleted' => false,
443
            'member' => [],
444
        ];
445
446
        $attributes = array_merge($attributes, $defaults);
447
448
        foreach ($member as $id) {
449
            $id = new ObjectId($id);
450
            if (!$this->userExists($id)) {
451
                throw new UserException('user does not exists', UserException::DOES_NOT_EXISTS);
452
            }
453
454
            if (!in_array($id, $attributes['member'], true)) {
455
                throw new GroupException('group can only hold a user once', GroupException::UNIQUE_MEMBER);
456
            }
457
458
            $attributes['member'][] = $id;
459
        }
460
461
        $result = $this->db->group->insertOne($attributes);
462
463
        return $result->getInsertedId();
464
    }
465
}
466