Completed
Pull Request — master (#64)
by Sean
03:17
created

LDAPMemberExtension::validate()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 21
rs 9.0534
cc 4
eloc 10
nc 4
nop 1
1
<?php
2
/**
3
 * Class LDAPMemberExtension.
4
 *
5
 * Adds mappings from AD attributes to SilverStripe {@link Member} fields.
6
 */
7
class LDAPMemberExtension 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...
8
{
9
    /**
10
     * @var array
11
     */
12
    private static $db = array(
0 ignored issues
show
Unused Code introduced by
The property $db 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...
13
        // Unique user identifier, same field is used by SAMLMemberExtension
14
        'GUID' => 'Varchar(50)',
15
        'Username' => 'Varchar(64)',
16
        'IsImportedFromLDAP' => 'Boolean',
17
        'IsExpired' => 'Boolean',
18
        'LastSynced' => 'SS_Datetime',
19
    );
20
21
    /**
22
     * These fields are used by {@link LDAPMemberSync} to map specific AD attributes
23
     * to {@link Member} fields.
24
     *
25
     * @var array
26
     * @config
27
     */
28
    private static $ldap_field_mappings = array(
0 ignored issues
show
Unused Code introduced by
The property $ldap_field_mappings 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...
29
        'givenname' => 'FirstName',
30
        'samaccountname' => 'Username',
31
        'sn' => 'Surname',
32
        'mail' => 'Email',
33
    );
34
35
    /**
36
     * The location (relative to /assets) where to save thumbnailphoto data.
37
     *
38
     * @var string
39
     * @config
40
     */
41
    private static $ldap_thumbnail_path = 'Uploads';
0 ignored issues
show
Unused Code introduced by
The property $ldap_thumbnail_path 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...
42
43
    /**
44
     * When enabled, LDAP managed Member records (IsImportedFromLDAP flag)
45
     * have their data written back to LDAP.
46
     *
47
     * This requires setting write permissions on the user configured in the LDAP
48
     * credentials, which is why this is disabled by default.
49
     *
50
     * Note that some constants must be configured in your environment file:
51
     * LDAP_DOMAIN - the base DN of the directory. e.g. "DN=mydomain,DC=com"
52
     *
53
     * @var bool
54
     * @config
55
     */
56
    private static $update_ldap_from_local = false;
0 ignored issues
show
Unused Code introduced by
The property $update_ldap_from_local 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...
57
58
    /**
59
     * If enabled, new Member records written are also created as users in LDAP.
60
     *
61
     * This requires setting write permissions on the user configured in the LDAP
62
     * credentials, which is why this is disabled by default.
63
     *
64
     * Note that some constants must be configured in your environment file:
65
     * LDAP_DOMAIN - the base DN of the directory. e.g. "DN=mydomain,DC=com"
66
     * LDAP_NEW_USERS_OBJECT_CATEGORY - the type of object. e.g. "CN=Person,CN=Schema,DC=mydomain,DC=com"
67
     * LDAP_NEW_USERS_DN - where to place users in the directory. e.g. "OU=Users,DC=mydomain,DC=com"
68
     *
69
     * @var bool
70
     * @config
71
     */
72
    private static $create_new_users_in_ldap = false;
0 ignored issues
show
Unused Code introduced by
The property $create_new_users_in_ldap 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...
73
74
    /**
75
     * @param FieldList $fields
76
     */
77
    public function updateCMSFields(FieldList $fields)
78
    {
79
        // Redo LDAP metadata fields as read-only and move to LDAP tab.
80
        $ldapMetadata = array();
81
        $fields->replaceField('IsImportedFromLDAP', $ldapMetadata[] = new ReadonlyField(
82
            'IsImportedFromLDAP',
83
            _t('LDAPMemberExtension.ISIMPORTEDFROMLDAP', 'Is user imported from LDAP/AD?')
84
        ));
85
        $fields->replaceField('GUID', $ldapMetadata[] = new ReadonlyField('GUID'));
86
        $fields->replaceField('IsExpired', $ldapMetadata[] = new ReadonlyField(
87
            'IsExpired',
88
            _t('LDAPMemberExtension.ISEXPIRED', 'Has user\'s LDAP/AD login expired?'))
89
        );
90
        $fields->replaceField('LastSynced', $ldapMetadata[] = new ReadonlyField(
91
            'LastSynced',
92
            _t('LDAPMemberExtension.LASTSYNCED', 'Last synced'))
93
        );
94
        $fields->addFieldsToTab('Root.LDAP', $ldapMetadata);
95
96
        if ($this->owner->IsImportedFromLDAP && $this->owner->config()->update_ldap_from_local) {
97
            $message = _t('LDAPMemberExtension.INFOIMPORTEDCANCHANGE',
98
                'This user is automatically imported from LDAP. '.
99
                    'Changing fields here will update them in LDAP.'
100
            );
101
        } elseif ($this->owner->IsImportedFromLDAP && !$this->owner->config()->update_ldap_from_local) {
102
            // Transform the automatically mapped fields into read-only. This doesn't
103
            // apply if updating LDAP from local is enabled, as changing data locally can be written back.
104
            foreach ($this->owner->config()->ldap_field_mappings as $name) {
105
                $field = $fields->dataFieldByName($name);
106
                if (!empty($field)) {
107
                    // Set to readonly, but not disabled so that the data is still sent to the
108
                    // server and doesn't break Member_Validator
109
                    $field->setReadonly(true);
110
                    $field->setTitle($field->Title()._t('LDAPMemberExtension.IMPORTEDFIELD', ' (imported)'));
111
                }
112
            }
113
            $message = _t(
114
                'LDAPMemberExtension.INFOIMPORTED',
115
                'This user is automatically imported from LDAP. '.
116
                    'Manual changes to imported fields will be removed upon sync.'
117
            );
118
        }
119
        $fields->addFieldToTab(
120
            'Root.Main',
121
            new LiteralField(
122
                'Info',
123
                sprintf('<p class="message warning">%s</p>', $message)
0 ignored issues
show
Bug introduced by
The variable $message does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
124
            ),
125
            'FirstName'
126
        );
127
    }
128
129
    public function validate(ValidationResult $validationResult)
130
    {
131
        if (!$this->owner->config()->create_new_users_in_ldap) {
132
            return;
133
        }
134
135
        // We allow empty Username for registration purposes, as we need to
136
        // create Member records with empty Username temporarily. Forms should explicitly
137
        // check for Username not being empty if they require it not to be.
138
        if (empty($this->owner->Username)) {
139
            return;
140
        }
141
142
        if (!preg_match('/^[a-z0-9\.]+$/', $this->owner->Username)) {
143
            $validationResult->error(
144
                'Username must only contain lowercase alphanumeric characters and dots.',
145
                'bad'
146
            );
147
            throw new ValidationException($validationResult);
148
        }
149
    }
150
151
    /**
152
     * Create the user in LDAP, provided this configuration is enabled
153
     * and a username was passed to a new Member record.
154
     *
155
     * Set a flag "Creating" so other extensions using on*() events can
156
     * detect whether it's in a state of being created, such as for
157
     * synchronising with other services when a user is being created
158
     * in LDAP for the first time.
159
     */
160
    public function onBeforeWrite()
161
    {
162
        $service = Injector::inst()->get('LDAPService');
163
        if (!$service->enabled()) {
164
            return;
165
        }
166
        if (!$this->owner->config()->create_new_users_in_ldap) {
167
            return;
168
        }
169
        if ($this->owner->Username && !$this->owner->IsImportedFromLDAP) {
170
            $service->createLDAPUser($this->owner);
171
            $this->owner->Creating = true;
172
        }
173
    }
174
175
    /**
176
     * Update the local data with LDAP, and ensure local membership is also set in
177
     * LDAP too. This writes into LDAP, provided that feature is enabled.
178
     */
179
    public function onAfterWrite()
180
    {
181
        $service = Injector::inst()->get('LDAPService');
182
        if (!$service->enabled()) {
183
            return;
184
        }
185
        if (!$this->owner->config()->update_ldap_from_local) {
186
            return;
187
        }
188
        if ($this->owner->IsImportedFromLDAP) {
189
            $service->updateLDAPFromMember($this->owner);
190
            $service->updateLDAPGroupsForMember($this->owner);
191
        }
192
    }
193
194
    /**
195
     * Triggered by {@link Member::logIn()} when successfully logged in,
196
     * this will update the Member record from AD data.
197
     */
198
    public function memberLoggedIn()
199
    {
200
        if ($this->owner->GUID) {
201
            Injector::inst()->get('LDAPService')->updateMemberFromLDAP($this->owner);
202
        }
203
    }
204
}
205