Completed
Push — master ( 287393...7ff50d )
by Raffael
18:27 queued 14:12
created

src/app/Balloon.App.Api/v1/User.php (11 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-2019 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\Server;
15
use Balloon\Server\AttributeDecorator;
16
use Balloon\Server\User\Exception;
17
use Micro\Http\Response;
18
use MongoDB\BSON\ObjectId;
19
20
class User
21
{
22
    /**
23
     * User.
24
     *
25
     * @var User
26
     */
27
    protected $user;
28
29
    /**
30
     * Server.
31
     *
32
     * @var Server
33
     */
34
    protected $server;
35
36
    /**
37
     * Decorator.
38
     *
39
     * @var AttributeDecorator
40
     */
41
    protected $decorator;
42
43
    /**
44
     * Initialize.
45
     */
46
    public function __construct(Server $server, AttributeDecorator $decorator)
47
    {
48
        $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...
49
        $this->server = $server;
50
        $this->decorator = $decorator;
51
    }
52
53
    /**
54
     * @apiDefine _getUser
55
     *
56
     * @apiParam (GET Parameter) {string[]} uid Either a single uid (user id) or a uname (username) must be given (admin privilege required).
57
     * @apiParam (GET Parameter) {string[]} uname Either a single uid (user id) or a uname (username) must be given (admin privilege required).
58
     *
59
     * @apiErrorExample {json} Error-Response (No admin privileges):
60
     * HTTP/1.1 403 Forbidden
61
     * {
62
     *      "status": 403,
63
     *      "data": {
64
     *          "error": "Balloon\\Filesystem\\Acl\\Exception\\Forbidden",
65
     *          "message": "submitted parameters require to have admin privileges",
66
     *          "code": 41
67
     *      }
68
     * }
69
     *
70
     * @apiErrorExample {json} Error-Response (User not found):
71
     * HTTP/1.1 404 Not Found
72
     * {
73
     *      "status": 404,
74
     *      "data": {
75
     *          "error": "Balloon\\Server\\User\\Exception",
76
     *          "message": "requested user was not found",
77
     *          "code": 53
78
     *      }
79
     * }
80
     *
81
     * @apiErrorExample {json} Error-Response (Invalid argument):
82
     * HTTP/1.1 400 Bad Request
83
     * {
84
     *      "status": 400,
85
     *      "data": {
86
     *          "error": "Balloon\\Exception\\InvalidArgument",
87
     *          "message": "provide either uid (user id) or uname (username)",
88
     *          "Code": 0
89
     *      }
90
     * }
91
     */
92
93
    /**
94
     * @apiDefine _getUsers
95
     *
96
     * @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.
97
     * @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.
98
     */
99
100
    /**
101
     * Get user instance.
102
     *
103
     * @param string $id
104
     * @param string $uname
105
     *
106
     * @return User
107
     */
108
    public function _getUser(?string $id = null, ?string $uname = null, bool $require_admin = false)
109
    {
110
        if (null !== $id || null !== $uname || true === $require_admin) {
111
            if ($this->user->isAdmin()) {
0 ignored issues
show
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...
112
                if (null !== $id && null !== $uname) {
113
                    throw new Exception\InvalidArgument(
114
                        'provide either id (user id) or uname (username)',
115
                        Exception\InvalidArgument::IDENTIFIER_NOT_UNIQUE
116
                    );
117
                }
118
119
                if (null !== $id) {
120
                    return $this->server->getUserById(new ObjectId($id));
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->server->ge...DB\BSON\ObjectId($id)); (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...
121
                }
122
123
                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...
124
            }
125
126
            throw new Exception\NotAdmin('submitted parameters require admin privileges');
127
        }
128
129
        return $this->user;
130
    }
131
132
    /**
133
     * @api {get} /api/v1/user/groups Group membership
134
     * @apiVersion 1.0.0
135
     * @apiName getGroups
136
     * @apiUse _getUser
137
     * @apiGroup User
138
     * @apiPermission none
139
     * @apiDescription Get all user groups
140
     * If you want to receive your own groups you have to leave the parameters uid and uname empty.
141
     * Requesting this api with parameter uid or uname requires admin privileges.
142
     *
143
     * @apiExample Example usage:
144
     * curl -XGET "https://SERVER/api/v1/user/groups?pretty"
145
     * curl -XGET "https://SERVER/api/v1/user/544627ed3c58891f058b4611/groups?pretty"
146
     * curl -XGET "https://SERVER/api/v1/user/groups?uname=loginuser&pretty"
147
     *
148
     * @apiSuccess {number} status Status Code
149
     * @apiSuccess {string[]} data  All groups with membership
150
     * @apiSuccessExample {json} Success-Response:
151
     * HTTP/1.1 200 OK
152
     * {
153
     *     "status": 200,
154
     *     "data": [
155
     *          "group1",
156
     *          "group2",
157
     *     ]
158
     * }
159
     *
160
     * @param string $uid
161
     * @param string $uname
162
     */
163
    public function getGroups(?string $uid = null, ?string $uname = null): Response
164
    {
165
        $result = $this->_getUser($uid, $uname)->getGroups();
166
167
        return (new Response())->setCode(200)->setBody([
168
            'status' => 200,
169
            'data' => $result,
170
        ]);
171
    }
172
173
    /**
174
     * @api {get} /api/v1/user/shares Share membership
175
     * @apiVersion 1.0.0
176
     * @apiName getShares
177
     * @apiUse _getUser
178
     * @apiGroup User
179
     * @apiPermission none
180
     * @apiDescription Get all shares
181
     * If you want to receive your own shares (member or owner) you have to leave the parameters uid and uname empty.
182
     * Requesting this api with parameter uid or uname requires admin privileges.
183
     *
184
     * @apiExample Example usage:
185
     * curl -XGET "https://SERVER/api/v1/user/shares?pretty"
186
     * curl -XGET "https://SERVER/api/v1/user/544627ed3c58891f058b4611/shares?pretty"
187
     * curl -XGET "https://SERVER/api/v1/user/shares?uname=loginuser&pretty"
188
     *
189
     * @apiSuccess {number} status Status Code
190
     * @apiSuccess {string[]} data  All shares with membership
191
     * @apiSuccessExample {json} Success-Response:
192
     * HTTP/1.1 200 OK
193
     * {
194
     *     "status": 200,
195
     *     "data": [
196
     *          "shareid1",
197
     *          "shareid2",
198
     *     ]
199
     * }
200
     *
201
     * @param string $uid
202
     * @param string $uname
203
     */
204
    public function getShares(?string $uid = null, ?string $uname = null): Response
205
    {
206
        $result = $this->_getUser($uid, $uname)->getShares();
207
208
        return (new Response())->setCode(200)->setBody([
209
            'status' => 200,
210
            'data' => $result,
211
        ]);
212
    }
213
214
    /**
215
     * @api {get} /api/v1/user/quota-usage Quota usage
216
     * @apiVersion 1.0.0
217
     * @apiName getQuotaUsage
218
     * @apiUse _getUser
219
     * @apiGroup User
220
     * @apiPermission none
221
     * @apiDescription Get user quota usage (including hard,soft,used and available space).
222
     * If you want to receive your own quota you have to leave the parameters uid and uname empty.
223
     * Requesting this api with parameter uid or uname requires admin privileges.
224
     *
225
     * @apiExample Example usage:
226
     * curl -XGET "https://SERVER/api/v1/user/quota-usage?pretty"
227
     * curl -XGET "https://SERVER/api/v1/user/544627ed3c58891f058b4611/quota-usage?pretty"
228
     * curl -XGET "https://SERVER/api/v1/user/quota-usage?uname=loginuser&pretty"
229
     *
230
     * @apiSuccess {number} status Status Code
231
     * @apiSuccess {object} data Quota stats
232
     * @apiSuccess {number} data.used Used quota in bytes
233
     * @apiSuccess {number} data.available Quota left in bytes
234
     * @apiSuccess {number} data.hard_quota Hard quota in bytes
235
     * @apiSuccess {number} data.soft_quota Soft quota (Warning) in bytes
236
     * @apiSuccessExample {json} Success-Response:
237
     * HTTP/1.1 200 OK
238
     * {
239
     *     "status": 200,
240
     *     "data": {
241
     *         "used": 15543092,
242
     *         "available": 5353166028,
243
     *         "hard_quota": 5368709120,
244
     *         "soft_quota": 5368709120
245
     *     }
246
     * }
247
     *
248
     * @param string $uid
249
     * @param string $uname
250
     */
251
    public function getQuotaUsage(?string $uid = null, ?string $uname = null): Response
252
    {
253
        $result = $this->_getUser($uid, $uname)->getQuotaUsage();
254
255
        return (new Response())->setCode(200)->setBody([
256
            'status' => 200,
257
            'data' => $result,
258
        ]);
259
    }
260
261
    /**
262
     * @api {get} /api/v1/user/attributes User attributes
263
     * @apiVersion 1.0.0
264
     * @apiName getAttributes
265
     * @apiUse _getUser
266
     * @apiGroup User
267
     * @apiPermission none
268
     * @apiDescription Get all user attributes including username, mail, id,....
269
     * If you want to receive your own attributes you have to leave the parameters uid and uname empty.
270
     * Requesting this api with parameter uid or uname requires admin privileges.
271
     *
272
     * @apiExample Example usage:
273
     * curl -XGET "https://SERVER/api/v1/user/attributes?pretty"
274
     * curl -XGET "https://SERVER/api/v1/user/544627ed3c58891f058b4611/attributes?pretty"
275
     * curl -XGET "https://SERVER/api/v1/user/attributes?uname=loginser&pretty"
276
     *
277
     * @apiSuccess (200 OK) {number} status Status Code
278
     * @apiSuccess (200 OK) {object[]} user attributes
279
     *
280
     * @apiSuccessExample {json} Success-Response:
281
     * HTTP/1.1 200 OK
282
     * {
283
     *      "status": 200,
284
     *      "data": [] //shortened
285
     * }
286
     *
287
     * @param string $uid
288
     * @param string $uname
289
     * @param string $attributes
290
     */
291
    public function getAttributes(?string $uid = null, ?string $uname = null, array $attributes = []): Response
292
    {
293
        $result = $this->decorator->decorate($this->_getUser($uid, $uname), $attributes);
0 ignored issues
show
$this->_getUser($uid, $uname) is of type object<Balloon\App\Api\v1\User>, but the function expects a object<Balloon\Server\RoleInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
294
295
        return (new Response())->setCode(200)->setBody([
296
            'status' => 200,
297
            'data' => $result,
298
        ]);
299
    }
300
301
    /**
302
     * @api {head} /api/v1/user?uid=:uid User exists?
303
     * @apiVersion 1.0.0
304
     * @apiName postQuota
305
     * @apiUse _getUser
306
     * @apiGroup User
307
     * @apiPermission admin
308
     * @apiDescription Check if user account exists
309
     *
310
     * @apiExample Example usage:
311
     * curl -XHEAD "https://SERVER/api/v1/user"
312
     * curl -XHEAD "https://SERVER/api/v1/user/544627ed3c58891f058b4611"
313
     * curl -XHEAD "https://SERVER/api/v1/user?uname=loginuser"
314
     *
315
     * @apiSuccessExample {json} Success-Response:
316
     * HTTP/1.1 204 No Content
317
     *
318
     * @param string $uname
319
     * @param string $uid
320
     */
321
    public function head(?string $uid = null, ?string $uname = null): Response
322
    {
323
        $result = $this->_getUser($uid, $uname);
0 ignored issues
show
$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...
324
325
        return (new Response())->setCode(204);
326
    }
327
328
    /**
329
     * @api {get} /api/v1/user/whoami Who am I?
330
     * @apiVersion 1.0.0
331
     * @apiName getWhoami
332
     * @apiUse _getUser
333
     * @apiGroup User
334
     * @apiPermission none
335
     * @apiDescription Get the username of the authenticated user
336
     * If you want to receive your own username you have to leave the parameters uid and uname empty.
337
     * Requesting this api with parameter uid or uname requires admin privileges.
338
     *
339
     * @apiExample Example usage:
340
     * curl -XGET "https://SERVER/api/v2/user/whoami?pretty"
341
     *
342
     * @apiSuccess {number} code HTTP status code
343
     * @apiSuccess {string} data Username
344
     * @apiSuccessExample {json} Success-Response:
345
     * HTTP/1.1 200 OK
346
     * {
347
     *     "code": 200,
348
     *     "data": "user"
349
     * }
350
     */
351
    public function getWhoami(array $attributes = []): Response
0 ignored issues
show
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...
352
    {
353
        return (new Response())->setCode(200)->setBody([
354
            'status' => 200,
355
            'data' => $this->_getUser()->getUsername(),
0 ignored issues
show
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...
356
        ]);
357
    }
358
359
    /**
360
     * @api {get} /api/v1/user/node-attribute-summary Node attribute summary
361
     * @apiVersion 1.0.0
362
     * @apiName getNodeAttributeSummary
363
     * @apiUse _getUser
364
     * @apiGroup User
365
     * @apiPermission none
366
     * @apiDescription Get summary and usage of specific node attributes
367
     * If you want to receive your own node summary you have to leave the parameters uid and uname empty.
368
     * Requesting this api with parameter uid or uname requires admin privileges.
369
     *
370
     * @apiExample Example usage:
371
     * curl -XGET "https://SERVER/api/v1/user/node-attribute-summary?pretty"
372
     * curl -XGET "https://SERVER/api/v1/user/544627ed3c58891f058b4611/node-attribute-summary?pretty"
373
     * curl -XGET "https://SERVER/api/v1/user/node-attribute-summary?uname=loginuser&pretty"
374
     *
375
     * @param string $id
376
     * @param string $uname
377
     * @param string $attributes
378
     */
379
    public function getNodeAttributeSummary(?string $id = null, ?string $uname = null, array $attributes = [], int $limit = 25): Response
380
    {
381
        $result = $this->_getUser($id, $uname)->getNodeAttributeSummary($attributes, $limit);
0 ignored issues
show
$attributes is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
382
383
        return (new Response())->setCode(200)->setBody([
384
            'status' => 200,
385
            'data' => $result,
386
        ]);
387
    }
388
389
    /**
390
     * @api {post} /api/v1/user/quota?uid=:uid Set quota
391
     * @apiVersion 1.0.0
392
     * @apiName postQuota
393
     * @apiUse _getUser
394
     * @apiGroup User
395
     * @apiPermission admin
396
     * @apiDescription Set quota for user
397
     *
398
     * @apiExample Example usage:
399
     * curl -XPOST -d hard=10000000 -d soft=999999 "https://SERVER/api/v1/user/quota"
400
     * curl -XPOST -d hard=10000000 -d soft=999999 "https://SERVER/api/v1/user/544627ed3c58891f058b4611/quota"
401
     * curl -XPOST -d hard=10000000 -d soft=999999 "https://SERVER/api/v1/user/quota?uname=loginuser"
402
     *
403
     * @apiParam (GET Parameter) {number} hard The new hard quota in bytes
404
     * @apiParam (GET Parameter) {number} soft The new soft quota in bytes
405
     *
406
     * @apiSuccessExample {json} Success-Response:
407
     * HTTP/1.1 204 No Content
408
     *
409
     * @param string $uname
410
     * @param string $uid
411
     */
412
    public function postQuota(int $hard, int $soft, ?string $uid = null, ?string $uname = null): Response
413
    {
414
        $result = $this->_getUser($uid, $uname, true)
0 ignored issues
show
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...
$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...
415
            ->setHardQuota($hard)
416
            ->setSoftQuota($soft);
417
418
        return (new Response())->setCode(204);
419
    }
420
}
421