Completed
Branch dev (d5d70c)
by Raffael
11:00
created

User   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 388
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 16
lcom 1
dl 0
loc 388
rs 10
c 0
b 0
f 0
cbo 4

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
C _getUser() 0 23 8
A getGroups() 0 9 1
A getShares() 0 9 1
A getQuotaUsage() 0 9 1
A getAttributes() 0 6 1
A head() 0 6 1
A getWhoami() 0 7 1
A postQuota() 0 8 1
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-2018 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Balloon\App\Api\v1;
13
14
use Balloon\Exception\InvalidArgument as InvalidArgumentException;
15
use Balloon\Filesystem\Acl\Exception\Forbidden as ForbiddenException;
16
use Balloon\Server;
17
use Balloon\Server\AttributeDecorator;
18
use Micro\Http\Response;
19
use MongoDB\BSON\ObjectId;
20
21
class User
22
{
23
    /**
24
     * User.
25
     *
26
     * @var User
27
     */
28
    protected $user;
29
30
    /**
31
     * Server.
32
     *
33
     * @var Server
34
     */
35
    protected $server;
36
37
    /**
38
     * Decorator.
39
     *
40
     * @var AttributeDecorator
41
     */
42
    protected $decorator;
43
44
    /**
45
     * Initialize.
46
     *
47
     * @param Server $server
48
     */
49
    public function __construct(Server $server, AttributeDecorator $decorator)
50
    {
51
        $this->user = $server->getIdentity();
0 ignored issues
show
Documentation Bug introduced by
It seems like $server->getIdentity() of type object<Balloon\Server\User> is incompatible with the declared type object<Balloon\App\Api\v1\User> of property $user.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
52
        $this->server = $server;
53
        $this->decorator = $decorator;
54
    }
55
56
    /**
57
     * @apiDefine _getUser
58
     *
59
     * @apiParam (GET Parameter) {string[]} uid Either a single uid (user id) or a uname (username) must be given (admin privilege required).
60
     * @apiParam (GET Parameter) {string[]} uname Either a single uid (user id) or a uname (username) must be given (admin privilege required).
61
     *
62
     * @apiErrorExample {json} Error-Response (No admin privileges):
63
     * HTTP/1.1 403 Forbidden
64
     * {
65
     *      "status": 403,
66
     *      "data": {
67
     *          "error": "Balloon\\Filesystem\\Acl\\Exception\\Forbidden",
68
     *          "message": "submitted parameters require to have admin privileges",
69
     *          "code": 41
70
     *      }
71
     * }
72
     *
73
     * @apiErrorExample {json} Error-Response (User not found):
74
     * HTTP/1.1 404 Not Found
75
     * {
76
     *      "status": 404,
77
     *      "data": {
78
     *          "error": "Balloon\\Server\\User\\Exception",
79
     *          "message": "requested user was not found",
80
     *          "code": 53
81
     *      }
82
     * }
83
     *
84
     * @apiErrorExample {json} Error-Response (Invalid argument):
85
     * HTTP/1.1 400 Bad Request
86
     * {
87
     *      "status": 400,
88
     *      "data": {
89
     *          "error": "Balloon\\Exception\\InvalidArgument",
90
     *          "message": "provide either uid (user id) or uname (username)",
91
     *          "Code": 0
92
     *      }
93
     * }
94
     */
95
96
    /**
97
     * @apiDefine _getUsers
98
     *
99
     * @apiParam (GET Parameter) {string[]} uid Either a single uid (user id) as string or multiple as an array or a single uname (username) as string or multiple usernames as array must be given.
100
     * @apiParam (GET Parameter) {string[]} uname Either a single uid (userid) as string or multiple as an array or a single uname (username) as string or multiple usernames as array must be given.
101
     */
102
103
    /**
104
     * Get user instance.
105
     *
106
     * @param string $uid
107
     * @param string $uname
108
     * @param bool   $require_admin
109
     *
110
     * @return User
111
     */
112
    public function _getUser(?string $uid = null, ?string $uname = null, bool $require_admin = false)
113
    {
114
        if (null !== $uid || null !== $uname || true === $require_admin) {
115
            if ($this->user->isAdmin()) {
0 ignored issues
show
Bug introduced by
The method isAdmin() does not seem to exist on object<Balloon\App\Api\v1\User>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
116
                if (null !== $uid && null !== $uname) {
117
                    throw new InvalidArgumentException('provide either uid (user id) or uname (username)');
118
                }
119
120
                if (null !== $uid) {
121
                    return $this->server->getUserById(new ObjectId($uid));
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->server->ge...B\BSON\ObjectId($uid)); (Balloon\Server\User) is incompatible with the return type documented by Balloon\App\Api\v1\User::_getUser of type Balloon\App\Api\v1\User.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
122
                }
123
124
                return $this->server->getUserByName($uname);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->server->getUserByName($uname); (Balloon\Server\User) is incompatible with the return type documented by Balloon\App\Api\v1\User::_getUser of type Balloon\App\Api\v1\User.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
125
            }
126
127
            throw new ForbiddenException(
128
                    'submitted parameters require to have admin privileges',
129
                    ForbiddenException::ADMIN_PRIV_REQUIRED
130
                );
131
        }
132
133
        return $this->user;
134
    }
135
136
    /**
137
     * @api {get} /api/v1/user/groups Group membership
138
     * @apiVersion 1.0.0
139
     * @apiName getGroups
140
     * @apiUse _getUser
141
     * @apiGroup User
142
     * @apiPermission none
143
     * @apiDescription Get all user groups
144
     * If you want to receive your own groups you have to leave the parameters uid and uname empty.
145
     * Requesting this api with parameter uid or uname requires admin privileges.
146
     *
147
     * @apiExample Example usage:
148
     * curl -XGET "https://SERVER/api/v1/user/groups?pretty"
149
     * curl -XGET "https://SERVER/api/v1/user/544627ed3c58891f058b4611/groups?pretty"
150
     * curl -XGET "https://SERVER/api/v1/user/groups?uname=loginuser&pretty"
151
     *
152
     * @apiSuccess {number} status Status Code
153
     * @apiSuccess {string[]} data  All groups with membership
154
     * @apiSuccessExample {json} Success-Response:
155
     * HTTP/1.1 200 OK
156
     * {
157
     *     "status": 200,
158
     *     "data": [
159
     *          "group1",
160
     *          "group2",
161
     *     ]
162
     * }
163
     *
164
     * @param string $uid
165
     * @param string $uname
166
     */
167
    public function getGroups(?string $uid = null, ?string $uname = null): Response
168
    {
169
        $result = $this->_getUser($uid, $uname)->getGroups();
170
171
        return (new Response())->setCode(200)->setBody([
172
            'code' => 200,
173
            'data' => $result,
174
        ]);
175
    }
176
177
    /**
178
     * @api {get} /api/v1/user/shares Share membership
179
     * @apiVersion 1.0.0
180
     * @apiName getShares
181
     * @apiUse _getUser
182
     * @apiGroup User
183
     * @apiPermission none
184
     * @apiDescription Get all shares
185
     * If you want to receive your own shares (member or owner) you have to leave the parameters uid and uname empty.
186
     * Requesting this api with parameter uid or uname requires admin privileges.
187
     *
188
     * @apiExample Example usage:
189
     * curl -XGET "https://SERVER/api/v1/user/shares?pretty"
190
     * curl -XGET "https://SERVER/api/v1/user/544627ed3c58891f058b4611/shares?pretty"
191
     * curl -XGET "https://SERVER/api/v1/user/shares?uname=loginuser&pretty"
192
     *
193
     * @apiSuccess {number} status Status Code
194
     * @apiSuccess {string[]} data  All shares with membership
195
     * @apiSuccessExample {json} Success-Response:
196
     * HTTP/1.1 200 OK
197
     * {
198
     *     "status": 200,
199
     *     "data": [
200
     *          "shareid1",
201
     *          "shareid2",
202
     *     ]
203
     * }
204
     *
205
     * @param string $uid
206
     * @param string $uname
207
     *
208
     * @return Response
209
     */
210
    public function getShares(?string $uid = null, ?string $uname = null): Response
211
    {
212
        $result = $this->_getUser($uid, $uname)->getShares();
213
214
        return (new Response())->setCode(200)->setBody([
215
            'code' => 200,
216
            'data' => $result,
217
        ]);
218
    }
219
220
    /**
221
     * @api {get} /api/v1/user/quota-usage Quota usage
222
     * @apiVersion 1.0.0
223
     * @apiName getQuotaUsage
224
     * @apiUse _getUser
225
     * @apiGroup User
226
     * @apiPermission none
227
     * @apiDescription Get user quota usage (including hard,soft,used and available space).
228
     * If you want to receive your own quota you have to leave the parameters uid and uname empty.
229
     * Requesting this api with parameter uid or uname requires admin privileges.
230
     *
231
     * @apiExample Example usage:
232
     * curl -XGET "https://SERVER/api/v1/user/quota-usage?pretty"
233
     * curl -XGET "https://SERVER/api/v1/user/544627ed3c58891f058b4611/quota-usage?pretty"
234
     * curl -XGET "https://SERVER/api/v1/user/quota-usage?uname=loginuser&pretty"
235
     *
236
     * @apiSuccess {number} status Status Code
237
     * @apiSuccess {object} data Quota stats
238
     * @apiSuccess {number} data.used Used quota in bytes
239
     * @apiSuccess {number} data.available Quota left in bytes
240
     * @apiSuccess {number} data.hard_quota Hard quota in bytes
241
     * @apiSuccess {number} data.soft_quota Soft quota (Warning) in bytes
242
     * @apiSuccessExample {json} Success-Response:
243
     * HTTP/1.1 200 OK
244
     * {
245
     *     "status": 200,
246
     *     "data": {
247
     *         "used": 15543092,
248
     *         "available": 5353166028,
249
     *         "hard_quota": 5368709120,
250
     *         "soft_quota": 5368709120
251
     *     }
252
     * }
253
     *
254
     * @param string $uid
255
     * @param string $uname
256
     *
257
     * @return Response
258
     */
259
    public function getQuotaUsage(?string $uid = null, ?string $uname = null): Response
260
    {
261
        $result = $this->_getUser($uid, $uname)->getQuotaUsage();
262
263
        return (new Response())->setCode(200)->setBody([
264
            'code' => 200,
265
            'data' => $result,
266
        ]);
267
    }
268
269
    /**
270
     * @api {get} /api/v1/user/attributes User attributes
271
     * @apiVersion 1.0.0
272
     * @apiName getAttributes
273
     * @apiUse _getUser
274
     * @apiGroup User
275
     * @apiPermission none
276
     * @apiDescription Get all user attributes including username, mail, id,....
277
     * If you want to receive your own attributes you have to leave the parameters uid and uname empty.
278
     * Requesting this api with parameter uid or uname requires admin privileges.
279
     *
280
     * @apiExample Example usage:
281
     * curl -XGET "https://SERVER/api/v1/user/attributes?pretty"
282
     * curl -XGET "https://SERVER/api/v1/user/544627ed3c58891f058b4611/attributes?pretty"
283
     * curl -XGET "https://SERVER/api/v1/user/attributes?uname=loginser&pretty"
284
     *
285
     * @apiSuccess (200 OK) {number} status Status Code
286
     * @apiSuccess (200 OK) {object[]} user attributes
287
     *
288
     * @apiSuccessExample {json} Success-Response:
289
     * HTTP/1.1 200 OK
290
     * {
291
     *      "status": 200,
292
     *      "data": [] //shortened
293
     * }
294
     *
295
     * @param string $uid
296
     * @param string $uname
297
     * @param string $attributes
298
     *
299
     * @return Response
300
     */
301
    public function getAttributes(?string $uid = null, ?string $uname = null, array $attributes = []): Response
302
    {
303
        $result = $this->_getUser($uid, $uname)->getAttribute($attributes);
0 ignored issues
show
Bug introduced by
The method getAttribute() does not exist on Balloon\App\Api\v1\User. Did you maybe mean getAttributes()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
304
305
        return (new Response())->setCode(200)->setBody($result);
306
    }
307
308
    /**
309
     * @api {head} /api/v1/user?uid=:uid User exists?
310
     * @apiVersion 1.0.0
311
     * @apiName postQuota
312
     * @apiUse _getUser
313
     * @apiGroup User
314
     * @apiPermission admin
315
     * @apiDescription Check if user account exists
316
     *
317
     * @apiExample Example usage:
318
     * curl -XHEAD "https://SERVER/api/v1/user"
319
     * curl -XHEAD "https://SERVER/api/v1/user/544627ed3c58891f058b4611"
320
     * curl -XHEAD "https://SERVER/api/v1/user?uname=loginuser"
321
     *
322
     * @apiSuccessExample {json} Success-Response:
323
     * HTTP/1.1 204 No Content
324
     *
325
     * @param string $uname
326
     * @param string $uid
327
     *
328
     * @return Response
329
     */
330
    public function head(?string $uid = null, ?string $uname = null): Response
331
    {
332
        $result = $this->_getUser($uid, $uname);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
333
334
        return (new Response())->setCode(204);
335
    }
336
337
    /**
338
     * @api {get} /api/v1/user/whoami Who am I?
339
     * @apiVersion 1.0.0
340
     * @apiName getWhoami
341
     * @apiUse _getUser
342
     * @apiGroup User
343
     * @apiPermission none
344
     * @apiDescription Get the username of the authenticated user
345
     * If you want to receive your own username you have to leave the parameters uid and uname empty.
346
     * Requesting this api with parameter uid or uname requires admin privileges.
347
     *
348
     * @apiExample Example usage:
349
     * curl -XGET "https://SERVER/api/v2/user/whoami?pretty"
350
     *
351
     * @apiSuccess {number} code HTTP status code
352
     * @apiSuccess {string} data Username
353
     * @apiSuccessExample {json} Success-Response:
354
     * HTTP/1.1 200 OK
355
     * {
356
     *     "code": 200,
357
     *     "data": "user"
358
     * }
359
     *
360
     * @param string $uid
0 ignored issues
show
Bug introduced by
There is no parameter named $uid. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
361
     * @param string $uname
0 ignored issues
show
Bug introduced by
There is no parameter named $uname. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
362
     *
363
     * @return Response
364
     */
365
    public function getWhoami(array $attributes = []): Response
0 ignored issues
show
Unused Code introduced by
The parameter $attributes is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
366
    {
367
        return (new Response())->setCode(200)->setBody([
368
            'code' => 200,
369
            'data' => $this->_getUser()->getUsername(),
0 ignored issues
show
Bug introduced by
The method getUsername() does not seem to exist on object<Balloon\App\Api\v1\User>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
370
        ]);
371
    }
372
373
    /**
374
     * @api {post} /api/v1/user/quota?uid=:uid Set quota
375
     * @apiVersion 1.0.0
376
     * @apiName postQuota
377
     * @apiUse _getUser
378
     * @apiGroup User
379
     * @apiPermission admin
380
     * @apiDescription Set quota for user
381
     *
382
     * @apiExample Example usage:
383
     * curl -XPOST -d hard=10000000 -d soft=999999 "https://SERVER/api/v1/user/quota"
384
     * curl -XPOST -d hard=10000000 -d soft=999999 "https://SERVER/api/v1/user/544627ed3c58891f058b4611/quota"
385
     * curl -XPOST -d hard=10000000 -d soft=999999 "https://SERVER/api/v1/user/quota?uname=loginuser"
386
     *
387
     * @apiParam (GET Parameter) {number} hard The new hard quota in bytes
388
     * @apiParam (GET Parameter) {number} soft The new soft quota in bytes
389
     *
390
     * @apiSuccessExample {json} Success-Response:
391
     * HTTP/1.1 204 No Content
392
     *
393
     * @param string $uname
394
     * @param string $uid
395
     * @param int    $hard
396
     * @param int    $soft
397
     *
398
     * @return Response
399
     */
400
    public function postQuota(int $hard, int $soft, ?string $uid = null, ?string $uname = null): Response
401
    {
402
        $result = $this->_getUser($uid, $uname)
0 ignored issues
show
Bug introduced by
The method setHardQuota() does not seem to exist on object<Balloon\App\Api\v1\User>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
403
            ->setHardQuota($hard)
404
            ->setSoftQuota($soft);
405
406
        return (new Response())->setCode(204);
407
    }
408
}
409