Issues (3)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/SharefileAdapter.php (3 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
namespace Kapersoft\FlysystemSharefile;
4
5
use Exception;
6
use League\Flysystem\Util;
7
use League\Flysystem\Config;
8
use Kapersoft\Sharefile\Client;
9
use League\Flysystem\Adapter\AbstractAdapter;
10
use League\Flysystem\Adapter\Polyfill\StreamedTrait;
11
use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
12
13
/**
14
 * Flysysten ShareFile Adapter.
15
 *
16
 * @author   Jan Willem Kaper <[email protected]>
17
 * @license  MIT (see License.txt)
18
 *
19
 * @link     http://github.com/kapersoft/flysystem-sharefile
20
 */
21
class SharefileAdapter extends AbstractAdapter
22
{
23
    use StreamedTrait;
24
    use NotSupportingVisibilityTrait;
25
26
    /** ShareFile access control constants */
27
    const CAN_ADD_FOLDER = 'CanAddFolder';
28
    const ADD_NODE = 'CanAddNode';
29
    const CAN_VIEW = 'CanView';
30
    const CAN_DOWNLOAD = 'CanDownload';
31
    const CAN_UPLOAD = 'CanUpload';
32
    const CAN_SEND = 'CanSend';
33
    const CAN_DELETE_CURRENT_ITEM = 'CanDeleteCurrentItem';
34
    const CAN_DELETE_CHILD_ITEMS = 'CanDeleteChildItems';
35
    const CAN_MANAGE_PERMISSIONS = 'CanManagePermissions';
36
    const CAN_CREATEOFFICE_DOCUMENTS = 'CanCreateOfficeDocuments';
37
38
    /**
39
     * ShareFile Client.
40
     *
41
     * @var  \Kapersoft\Sharefile\Client;
42
     * */
43
    protected $client;
44
45
    /**
46
     * Indicated if metadata should include the ShareFile item array.
47
     *
48
     * @var  bool
49
     * */
50
    protected $returnShareFileItem;
51
52
    /**
53
     * SharefileAdapter constructor.
54
     *
55
     * @param Client $client              Instance of Kapersoft\Sharefile\Client
56
     * @param string $prefix              Folder prefix
57
     * @param bool   $returnShareFileItem Indicated if getMetadatsa/listContents should return ShareFile item array.
58
     *
59
     * @param string $prefix
60
     */
61 723
    public function __construct(Client $client, string $prefix = '', bool $returnShareFileItem = false)
62
    {
63 723
        $this->client = $client;
64
65 723
        $this->returnShareFileItem = $returnShareFileItem;
66
67 723
        $this->setPathPrefix($prefix);
68 723
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73 240
    public function has($path)
74
    {
75 240
        return $this->getMetadata($path);
76
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81 60 View Code Duplication
    public function read($path)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
82
    {
83 60
        if (!$item = $this->getItemByPath($path)) {
84 30
            return false;
85
        }
86
87 30
        if (!$this->checkAccessControl($item, self::CAN_DOWNLOAD)) {
88
            return false;
89
        }
90
91 30
        $contents = $this->client->getItemContents($item['Id']);
92
93 30
        return $this->mapItemInfo($item, Util::dirname($path), $contents);
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99 60
    public function listContents($directory = '', $recursive = false)
100
    {
101 60
        if (!$item = $this->getItemByPath($directory)) {
102 30
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface League\Flysystem\ReadInterface::listContents of type array.

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...
103
        }
104
105 30
        return $this->buildItemList($item, $directory, $recursive);
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111 540
    public function getMetadata($path)
112
    {
113 540
        if (!$item = $this->getItemByPath($path)) {
114 180
            return false;
115
        }
116 360
        $metadata = $this->mapItemInfo($item, Util::dirname($path));
117
118 360
        if (in_array($path, ['/', ''], true)) {
119
            $metadata['path'] = $path;
120
        }
121
122 360
        return $metadata;
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128 60
    public function getSize($path)
129
    {
130 60
        return $this->getMetadata($path);
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136
    public function getMimetype($path)
137
    {
138
        return $this->getMetadata($path);
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144 60
    public function getTimestamp($path)
145
    {
146 60
        return $this->getmetaData($path);
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152 150
    public function write($path, $contents, Config $config = null)
153
    {
154 150
        return $this->uploadFile($path, $contents, true);
155
    }
156
157
    /**
158
     * {@inheritdoc}
159
     */
160 150
    public function update($path, $contents, Config $config = null)
161
    {
162 150
        return $this->uploadFile($path, $contents, true);
163
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168 180
    public function rename($path, $newpath)
169
    {
170 180
        if (!$targetFolderItem = $this->getItemByPath(Util::dirname($newpath))) {
171 30
            return false;
172
        }
173
174 150
        if (!$this->checkAccessControl($targetFolderItem, self::CAN_UPLOAD)) {
175 60
            return false;
176
        }
177
178 90
        if (!$item = $this->getItemByPath($path)) {
179 30
            return false;
180
        }
181
182
        $data = [
183 60
            'FileName' =>  basename($newpath),
184 60
            'Name' =>  basename($newpath),
185
            'Parent' =>  [
186 60
                'Id' => $targetFolderItem['Id'],
187
            ],
188
        ];
189
190 60
        $this->client->updateItem($item['Id'], $data);
191
192 60
        return is_array($this->has($newpath));
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     */
198 180
    public function copy($path, $newpath)
199
    {
200 180
        if (!$targetFolderItem = $this->getItemByPath(Util::dirname($newpath))) {
201 30
            return false;
202
        }
203
204 150
        if (!$this->checkAccessControl($targetFolderItem, self::CAN_UPLOAD)) {
205 60
            return false;
206
        }
207
208 90
        if (!$item = $this->getItemByPath($path)) {
209 30
            return false;
210
        }
211
212 60
        if (strcasecmp(Util::dirname($path), Util::dirname($newpath)) != 0 &&
213 60
            strcasecmp(basename($path), basename($newpath)) == 0) {
214 30
            $this->client->copyItem($targetFolderItem['Id'], $item['Id'], true);
215
        } else {
216 30
            $contents = $this->client->getItemContents($item['Id']);
217 30
            $this->uploadFile($newpath, $contents, true);
218
        }
219
220 60
        return is_array($this->has($newpath));
221
    }
222
223
    /**
224
     * {@inheritdoc}
225
     */
226 60
    public function delete($path)
227
    {
228 60
        return $this->deleteDir($path);
229
    }
230
231
    /**
232
     * {@inheritdoc}
233
     */
234 60
    public function deleteDir($dirname)
235
    {
236 60
        if (!$item = $this->getItemByPath($dirname)) {
237 30
            return false;
238
        }
239
240 30
        if (!$this->checkAccessControl($item, self::CAN_DELETE_CURRENT_ITEM)) {
241
            return false;
242
        }
243
244 30
        $this->client->deleteItem($item['Id']);
245
246 30
        return $this->has($dirname) === false;
247
    }
248
249
    /**
250
     * {@inheritdoc}
251
     */
252 120
    public function createDir($dirname, Config $config = null)
253
    {
254 120
        $parentFolder = Util::dirname($dirname);
255 120
        $folder = basename($dirname);
256
257 120
        if (!$parentFolderItem = $this->getItemByPath($parentFolder)) {
258 30
            return false;
259
        }
260
261 90
        if (!$this->checkAccessControl($parentFolderItem, self::CAN_ADD_FOLDER)) {
262 60
            return false;
263
        }
264
265 30
        $this->client->createFolder($parentFolderItem['Id'], $folder, $folder, true);
266
267 30
        return $this->has($dirname);
268
    }
269
270
    /**
271
     * {@inheritdoc}
272
     */
273 120
    public function put($path, $contents)
274
    {
275 120
        return $this->uploadFile($path, $contents, true);
276
    }
277
278
    /**
279
     * {@inheritdoc}
280
     */
281 30 View Code Duplication
    public function readAndDelete($path)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282
    {
283 30
        if (!$item = $this->getItemByPath($path)) {
284 30
            return false;
285
        }
286
287
        if (!$this->checkAccessControl($item, self::CAN_DOWNLOAD) ||
288
            !$this->checkAccessControl($item, self::CAN_DELETE_CURRENT_ITEM)) {
289
            return false;
290
        }
291
292
        $itemContents = $this->client->getItemContents($item['Id']);
293
294
        $this->delete($path);
295
296
        return $itemContents;
297
    }
298
299
    /**
300
     * Returns ShareFile client.
301
     *
302
     * @return Client
303
     */
304 3
    public function getClient(): Client
305
    {
306 3
        return $this->client;
307
    }
308
309
    /**
310
     * Upload a file to ShareFile.
311
     *
312
     * @param string $path      File path
313
     * @param string $contents  Contents of the file
314
     * @param bool   $overwrite Overwrite file is it exists
315
     *
316
     * @return array|false
317
     */
318 240
    protected function uploadFile(string $path, string $contents, bool $overwrite = false)
319
    {
320 240
        if (!$parentFolderItem = $this->getItemByPath(Util::dirname($path))) {
321 30
            return false;
322
        }
323
324 210
        if (!$this->checkAccessControl($parentFolderItem, self::CAN_UPLOAD)) {
325 60
            return false;
326
        }
327
328 150
        $filename = $this->prepareUploadFile(basename($path), $contents);
329
330 150
        $this->client->uploadFileStandard($filename, $parentFolderItem['Id'], false, $overwrite);
331
332 150
        $this->removeUploadFile($filename);
333
334 150
        if ($metadata = $this->getMetadata($path)) {
335 150
            $metadata['contents'] = $contents;
336
337 150
            return $metadata;
338
        }
339
340
        return false;
341
    }
342
343
    /**
344
     * Prepares upload-file.
345
     *
346
     * @param string $filename Filename
347
     * @param string $contents Contents of the file
348
     *
349
     * @return string
350
     */
351 150
    protected function prepareUploadFile(string $filename, string $contents):string
352
    {
353 150
        $filename = tempnam(sys_get_temp_dir(), '') . '/' . $filename;
354 150
        unlink(Util::dirname($filename));
355 150
        mkdir(Util::dirname($filename));
356 150
        file_put_contents($filename, $contents);
357
358 150
        return $filename;
359
    }
360
361
    /**
362
     * Removes temporary directory and upload-file.
363
     *
364
     * @param string $filename Filename.
365
     */
366 150
    protected function removeUploadFile(string $filename)
367
    {
368 150
        unlink($filename);
369 150
        rmdir(Util::dirname($filename));
370 150
    }
371
372
    /**
373
     * Map ShareFile item to FlySystem metadata.
374
     *
375
     * @param array       $item     ShareFile item
376
     * @param string      $path     Base path
377
     * @param string|null $contents Contents of the file (optional)
378
     *
379
     * @return array
380
     */
381 420
    protected function mapItemInfo(array $item, string $path = '', string $contents = null): array
382
    {
383 420
        $timestamp = $item['ClientModifiedDate'] ?? $item['ClientCreatedDate'] ??
384 420
            $item['CreationDate'] ?? $item['ProgenyEditDate'] ?? '';
385 420
        $timestamp = !empty($timestamp) ? strtotime($timestamp) : false;
386
387 420
        if ($path == '.') {
388
            $path = '';
389
        }
390 420
        $path = trim($path . '/' . $item['FileName'], '/');
391
392 420
        if ($this->isShareFileApiModelsFile($item)) {
393 420
            $mimetype = Util::guessMimeType($item['FileName'], $contents);
394 420
            $type = 'file';
395
        } else {
396 30
            $mimetype = 'inode/directory';
397 30
            $type = 'dir';
398
        }
399
400 420
        return array_merge(
401
            [
402 420
                'timestamp' => $timestamp,
403 420
                'path' => $path,
404 420
                'mimetype' => $mimetype,
405 420
                'dirname' => pathinfo($path, PATHINFO_DIRNAME),
406 420
                'extension' => pathinfo($item['FileName'], PATHINFO_EXTENSION),
407 420
                'filename' => pathinfo($item['FileName'], PATHINFO_FILENAME),
408 420
                'basename' => pathinfo($item['FileName'], PATHINFO_FILENAME),
409 420
                'type' => $type,
410 420
                'size' => $item['FileSizeBytes'],
411 420
                'contents' =>  !empty($contents) ? $contents : false,
412
                'stream' => false,
413
            ],
414 420
            $this->returnShareFileItem ? ['sharefile_item' => $item] : []
415
        );
416
    }
417
418
    /**
419
     * Map list of ShareFile items with metadata.
420
     *
421
     * @param array  $items List of ShareFile items
422
     * @param string $path  Base path
423
     *
424
     * @return array
425
     */
426 30
    protected function mapItemList(array $items, string $path):array
427
    {
428 30
        return array_map(
429 30
            function ($item) use ($path) {
430 30
                return $this->mapItemInfo($item, $path);
431 30
            },
432 30
            $items
433
        );
434
    }
435
436
    /**
437
     * Build metadata list from ShareFile item.
438
     *
439
     * @param array  $item       ShareFile item
440
     * @param string $path       Path of the given ShareFile item
441
     * @param bool   $recursive  Recursive mode
442
     *
443
     * @return array
444
     */
445 30
    protected function buildItemList(array $item, string $path, bool $recursive = false):array
446
    {
447 30
        if ($this->isShareFileApiModelsFile($item)) {
448 30
            return [];
449
        }
450
451 30
        $children = $this->client->getItemById($item['Id'], true);
452
453 30
        if ($children['FileCount'] < 2 || !isset($children['Children'])) {
454
            return [];
455
        }
456
457 30
        $children = $this->removeAllExceptFilesAndFolders($children['Children']);
458
459 30
        $itemList = $this->mapItemList($children, $path);
460
461 30
        if ($recursive) {
462 30
            foreach ($children as $child) {
463 30
                $path = $path . '/' . $child['FileName'];
464
465 30
                $itemList = array_merge(
466 30
                    $itemList,
467 30
                    $this->buildItemList($child, $path, true)
468
                );
469
            }
470
        }
471
472 30
        return $itemList;
473
    }
474
475
    /**
476
     * Remove all items except files and folders in the given array of ShareFile items.
477
     *
478
     * @param array $items Array of ShareFile items
479
     *
480
     * @return array
481
     */
482 30
    protected function removeAllExceptFilesAndFolders(array $items):array
483
    {
484 30
        return array_filter(
485 30
            $items,
486 30
            function ($item) {
487 30
                return $this->isShareFileApiModelsFolder($item) || $this->isShareFileApiModelsFile($item);
488 30
            }
489
        );
490
    }
491
492
    /**
493
     * Check if ShareFile item is a ShareFile.Api.Models.Folder type.
494
     *
495
     * @param array $item
496
     *
497
     * @return bool
498
     */
499 570
    protected function isShareFileApiModelsFolder(array $item):bool
500
    {
501 570
        return $item['odata.type'] == 'ShareFile.Api.Models.Folder';
502
    }
503
504
    /**
505
     * Check if ShareFile item is a ShareFile.Api.Models.File type.
506
     *
507
     * @param array $item
508
     *
509
     * @return bool
510
     */
511 570
    protected function isShareFileApiModelsFile(array $item):bool
512
    {
513 570
        return $item['odata.type'] == 'ShareFile.Api.Models.File';
514
    }
515
516
    /**
517
     * Get ShareFile item using path.
518
     *
519
     * @param string $path Path of the requested file
520
     *
521
     * @return array|false
522
     *
523
     * @throws Exception
524
     */
525 720
    protected function getItemByPath(string $path)
526
    {
527 720
        if ($path == '.') {
528
            $path = '';
529
        }
530 720
        $path = '/' . trim($this->applyPathPrefix($path), '/');
531
532
        try {
533 720
            $item = $this->client->getItemByPath($path);
534 570
            if ($this->isShareFileApiModelsFolder($item) || $this->isShareFileApiModelsFile($item)) {
535 570
                return $item;
536
            }
537 240
        } catch (exception $e) {
538 240
            return false;
539
        }
540
541
        return false;
542
    }
543
544
    /**
545
     * Check access control of a ShareFile item.
546
     *
547
     * @param array  $item ShareFile item
548
     * @param string $rule Access rule
549
     *
550
     * @return bool
551
     */
552 420
    protected function checkAccessControl(array $item, string $rule):bool
553
    {
554 420
        if ($this->isShareFileApiModelsFile($item)) {
555 60
            $item = $this->client->getItemById($item['Parent']['Id']);
556 60
            if ($rule == self::CAN_DELETE_CURRENT_ITEM) {
557 30
                $rule = self::CAN_DELETE_CHILD_ITEMS;
558
            }
559
        }
560
561 420
        if (isset($item['Info'][$rule])) {
562 390
            return $item['Info'][$rule] == 1;
563
        } else {
564 30
            return false;
565
        }
566
    }
567
}
568