Completed
Push — master ( 67ead9...61a703 )
by Daniel
12s
created

SecureEditableFileField   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 144
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 7
dl 0
loc 144
ccs 0
cts 51
cp 0
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getIsSecurityEnabled() 0 15 3
A onBeforeWrite() 0 4 1
B makeSecure() 0 23 6
A findAdminGroup() 0 5 1
A getIsSecure() 0 4 1
B requireDefaultRecords() 0 22 4
B isFolderSecured() 0 18 9
1
<?php
2
3
/**
4
 * Provides additional file security for uploaded files when the securefiles module is installed
5
 *
6
 * {@see EditableFileField}
7
 */
8
class SecureEditableFileField extends DataExtension
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

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