Completed
Pull Request — master (#647)
by Robbie
05:30 queued 03:19
created

SecureEditableFileField::makeSecure()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 8.5906
c 0
b 0
f 0
cc 6
eloc 12
nc 4
nop 0
1
<?php
2
3
namespace SilverStripe\UserForms\Extension;
4
5
use SilverStripe\Assets\Folder;
6
use SilverStripe\ORM\DataExtension;
7
use SilverStripe\ORM\DB;
8
use SilverStripe\Security\Group;
9
use SilverStripe\Security\Permission;
10
use SilverStripe\UserForms\Model\EditableFormField\EditableFileField;
11
12
/**
13
 * Provides additional file security for uploaded files when the securefiles module is installed
14
 *
15
 * {@see EditableFileField}
16
 */
17
class SecureEditableFileField extends DataExtension
18
{
19
20
    /**
21
     * Path to secure files location under assets
22
     *
23
     * @config
24
     * @var type
25
     */
26
    private static $secure_folder_name = 'SecureUploads';
0 ignored issues
show
Unused Code introduced by
The property $secure_folder_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
27
28
    /**
29
     * Disable file security if a user-defined mechanism is in place
30
     *
31
     * @config
32
     * @var bool
33
     */
34
    private static $disable_security = false;
0 ignored issues
show
Unused Code introduced by
The property $disable_security is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
35
36
    /*
37
     * Check if file security is enabled
38
     *
39
     * @return bool
40
     */
41
    public function getIsSecurityEnabled()
42
    {
43
        // Skip if requested
44
        if ($this->owner->config()->disable_security) {
45
            return false;
46
        }
47
48
        // Check for necessary security module
49
        if (!class_exists('SecureFileExtension')) {
50
            trigger_error('SecureEditableFileField requires secureassets module', E_USER_WARNING);
51
            return false;
52
        }
53
54
        return true;
55
    }
56
57
    public function requireDefaultRecords()
58
    {
59
        // Skip if disabled
60
        if (!$this->getIsSecurityEnabled()) {
61
            return;
62
        }
63
64
        // Update all instances of editablefilefield which do NOT have a secure folder assigned
65
        foreach (EditableFileField::get() as $fileField) {
66
            // Skip if secured
67
            if ($fileField->getIsSecure()) {
68
                continue;
69
            }
70
71
            // Force this field to secure itself on write
72
            $fileField->write(false, false, true);
73
            DB::alteration_message(
74
                "Restricting editable file field \"{$fileField->Title}\" to secure folder",
75
                "changed"
76
            );
77
        }
78
    }
79
80
    /**
81
     * Secure this field before saving
82
     */
83
    public function onBeforeWrite()
84
    {
85
        $this->makeSecure();
86
    }
87
88
    /**
89
     * Ensure this field is secured, but does not write changes to the database
90
     */
91
    public function makeSecure()
92
    {
93
        // Skip if disabled or already secure
94
        if (!$this->getIsSecurityEnabled() || $this->owner->getIsSecure()) {
95
            return;
96
        }
97
98
        // Ensure folder exists
99
        $folder = $this->owner->Folder();
100
        if (!$folder || !$folder->exists()) {
101
            // Create new folder in default location
102
            $folder = Folder::find_or_make($this->owner->config()->secure_folder_name);
103
            $this->owner->FolderID = $folder->ID;
104
        } elseif ($this->isFolderSecured($folder)) {
105
            // If folder exists and is secure stop
106
            return;
107
        }
108
109
        // Make secure
110
        $folder->CanViewType = 'OnlyTheseUsers';
111
        $folder->ViewerGroups()->add($this->findAdminGroup());
112
        $folder->write();
113
    }
114
115
    /**
116
     * Find target group to record
117
     *
118
     * @return Group
119
     */
120
    protected function findAdminGroup()
121
    {
122
        singleton(Group::class)->requireDefaultRecords();
123
        return Permission::get_groups_by_permission('ADMIN')->First();
124
    }
125
126
    /**
127
     * Determine if the field is secure
128
     *
129
     * @return bool
130
     */
131
    public function getIsSecure()
132
    {
133
        return $this->isFolderSecured($this->owner->Folder());
134
    }
135
136
    /**
137
     * Check if a Folder object is secure
138
     *
139
     * @param Folder $folder
140
     * @return boolean
141
     */
142
    protected function isFolderSecured($folder)
143
    {
144
        if (! ($folder instanceof Folder) || !$folder->exists()) {
145
            return false;
146
        }
147
148
        switch ($folder->CanViewType) {
149
            case 'OnlyTheseUsers':
150
                return true;
151
            case 'Inherit':
152
                $parent = $folder->Parent();
153
                return $parent && $parent->exists() && $this->isFolderSecured($parent);
0 ignored issues
show
Compatibility introduced by
$parent of type object<SilverStripe\Assets\File> is not a sub-type of object<SilverStripe\Assets\Folder>. It seems like you assume a child class of the class SilverStripe\Assets\File to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
154
            case 'Anyone':
155
            case 'LoggedInUsers':
156
            default:
157
                return false;
158
        }
159
    }
160
}
161