Passed
Push — master ( 5a9348...48ccd3 )
by Thomas
03:36
created

EncryptedDBFile::sendDecryptedFile()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 16
c 3
b 1
f 0
dl 0
loc 25
rs 9.4222
cc 5
nc 8
nop 0
1
<?php
2
3
namespace LeKoala\Encrypt;
4
5
use Exception;
6
use SilverStripe\Assets\File;
7
use SilverStripe\Control\Director;
8
use SilverStripe\ORM\DataExtension;
9
use SilverStripe\Core\Config\Config;
10
use SilverStripe\Versioned\Versioned;
11
use ParagonIE\CipherSweet\CipherSweet;
12
use ParagonIE\CipherSweet\EncryptedFile;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, LeKoala\Encrypt\EncryptedFile. Consider defining an alias.

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...
13
use SilverStripe\Assets\Flysystem\FlysystemAssetStore;
14
15
/**
16
 * Safe and encrypted content file
17
 * Also make sure that files are not public assets! => use htaccess
18
 * @property bool $Encrypted
19
 * @property File|EncryptedDBFile $owner
20
 */
21
class EncryptedDBFile extends DataExtension
22
{
23
    /**
24
     * @var EncryptedFile
25
     */
26
    protected static $encryptionEngine;
27
28
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
29
        "Encrypted" => "Boolean",
30
    ];
31
32
    /**
33
     * @return string
34
     */
35
    public function getDecryptionLink()
36
    {
37
        $data = [
38
            "ID" => $this->owner->ID,
0 ignored issues
show
Bug Best Practice introduced by
The property ID does not exist on LeKoala\Encrypt\EncryptedDBFile. Did you maybe forget to declare it?
Loading history...
39
            "Hash" => substr($this->owner->File->Hash, 0, 10),
0 ignored issues
show
Bug Best Practice introduced by
The property File does not exist on LeKoala\Encrypt\EncryptedDBFile. Did you maybe forget to declare it?
Loading history...
40
        ];
41
        $url = "__decrypt/?" . http_build_query($data);
42
        return Director::absoluteURL($url);
43
    }
44
45
    /**
46
     * Check if the actual file on the filesystem is encrypted
47
     * You might also use the Encrypted field that should be accurate
48
     *
49
     * @return boolean
50
     */
51
    public function isEncrypted()
52
    {
53
        $stream = $this->owner->getStream();
0 ignored issues
show
Bug introduced by
The method getStream() does not exist on LeKoala\Encrypt\EncryptedDBFile. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

53
        /** @scrutinizer ignore-call */ 
54
        $stream = $this->owner->getStream();

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...
54
        if (!$stream) {
0 ignored issues
show
introduced by
$stream is of type resource, thus it always evaluated to false.
Loading history...
55
            return false;
56
        }
57
        $encFile = EncryptHelper::getEncryptedFileInstance();
58
        return $encFile->isStreamEncrypted($stream);
59
    }
60
61
    /**
62
     * @param boolean $forceStatus
63
     * @param boolean $write
64
     * @return boolean
65
     */
66
    public function updateEncryptionStatus($forceStatus = null, $write = true)
67
    {
68
        if ($forceStatus !== null) {
69
            $this->owner->Encrypted = (bool)$forceStatus;
70
        } else {
71
            if ($this->isEncrypted()) {
72
                $this->owner->Encrypted = true;
73
            } else {
74
                $this->owner->Encrypted = false;
75
            }
76
        }
77
        if ($write) {
78
            if ($this->owner->hasExtension(Versioned::class)) {
0 ignored issues
show
Bug introduced by
The method hasExtension() does not exist on LeKoala\Encrypt\EncryptedDBFile. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

78
            if ($this->owner->/** @scrutinizer ignore-call */ hasExtension(Versioned::class)) {

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...
79
                $this->owner->writeWithoutVersion();
0 ignored issues
show
Bug introduced by
The method writeWithoutVersion() does not exist on LeKoala\Encrypt\EncryptedDBFile. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

79
                $this->owner->/** @scrutinizer ignore-call */ 
80
                              writeWithoutVersion();

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...
80
            } else {
81
                $this->owner->write();
0 ignored issues
show
Bug introduced by
The method write() does not exist on LeKoala\Encrypt\EncryptedDBFile. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

81
                $this->owner->/** @scrutinizer ignore-call */ 
82
                              write();

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...
82
            }
83
        }
84
        return $this->owner->Encrypted;
85
    }
86
87
    /**
88
     * Output file using regular php
89
     * Does not send headers, see EncryptHelper::sendDecryptedFile
90
     *
91
     * @throws Exception
92
     * @return void
93
     */
94
    public function sendDecryptedFile()
95
    {
96
        if (ob_get_level()) {
97
            ob_end_clean();
98
        }
99
        $stream = $this->owner->getStream();
100
        if ($this->owner->Encrypted) {
101
            $encFile = EncryptHelper::getEncryptedFileInstance();
102
            $output = fopen('php://temp', 'w+b');
103
104
            // We need to decrypt stream
105
            if ($encFile->isStreamEncrypted($stream)) {
106
                $success = $encFile->decryptStream($stream, $output);
107
                if (!$success) {
108
                    throw new Exception("Failed to decrypt stream");
109
                }
110
111
                // Rewind first
112
                rewind($output);
113
                fpassthru($output);
114
            } else {
115
                fpassthru($stream);
116
            }
117
        } else {
118
            fpassthru($stream);
119
        }
120
    }
121
122
    /**
123
     * Files are not encrypted automatically
124
     * Call this method to encrypt them
125
     *
126
     * @throws Exception
127
     * @param bool $write
128
     * @return bool
129
     */
130
    public function encryptFileIfNeeded($write = true)
131
    {
132
        // Already mark as encrypted
133
        if ($this->owner->Encrypted) {
134
            return true;
135
        }
136
        if (!$this->owner->exists()) {
0 ignored issues
show
Bug introduced by
The method exists() does not exist on LeKoala\Encrypt\EncryptedDBFile. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

136
        if (!$this->owner->/** @scrutinizer ignore-call */ exists()) {

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...
137
            throw new Exception("File does not exist");
138
        }
139
        $stream = $this->owner->getStream();
140
        if (!$stream) {
0 ignored issues
show
introduced by
$stream is of type resource, thus it always evaluated to false.
Loading history...
141
            throw new Exception("Failed to get stream");
142
        }
143
144
        $encFile = EncryptHelper::getEncryptedFileInstance();
145
        $isEncrypted = $encFile->isStreamEncrypted($stream);
146
147
        // It's not yet encrypted
148
        if (!$isEncrypted) {
149
            // php://temp is not a file path, it's a pseudo protocol that always creates a new random temp file when used.
150
            $output = fopen('php://temp', 'wb');
151
            $success =  $encFile->encryptStream($stream, $output);
152
            if (!$success) {
153
                throw new Exception("Failed to encrypt stream");
154
            }
155
            // dont forget to rewind the stream !
156
            rewind($output);
157
158
            // This is really ugly, see https://github.com/silverstripe/silverstripe-assets/issues/467
159
            $configFlag = FlysystemAssetStore::config()->keep_empty_dirs;
160
            Config::modify()->set(FlysystemAssetStore::class, 'keep_empty_dirs', true);
161
            $fileResult = $this->owner->setFromStream($output, $this->owner->getFilename());
162
            // Mark as encrypted in db
163
            $this->updateEncryptionStatus(true, $write);
164
            Config::modify()->set(FlysystemAssetStore::class, 'keep_empty_dirs', $configFlag);
165
166
            return true;
167
        }
168
169
        if ($this->owner->Encrypted != $isEncrypted) {
170
            $this->updateEncryptionStatus($isEncrypted, $write);
171
        }
172
173
        return $isEncrypted;
174
    }
175
}
176