Completed
Pull Request — master (#64)
by Sean
02:49 queued 26s
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
        $message = null;
97
        if ($this->owner->IsImportedFromLDAP && $this->owner->config()->update_ldap_from_local) {
98
            $message = _t('LDAPMemberExtension.INFOIMPORTEDCANCHANGE',
99
                'This user is automatically imported from LDAP. '.
100
                    'Changing fields here will update them in LDAP.'
101
            );
102
        } elseif ($this->owner->IsImportedFromLDAP && !$this->owner->config()->update_ldap_from_local) {
103
            // Transform the automatically mapped fields into read-only. This doesn't
104
            // apply if updating LDAP from local is enabled, as changing data locally can be written back.
105
            foreach ($this->owner->config()->ldap_field_mappings as $name) {
106
                $field = $fields->dataFieldByName($name);
107
                if (!empty($field)) {
108
                    // Set to readonly, but not disabled so that the data is still sent to the
109
                    // server and doesn't break Member_Validator
110
                    $field->setReadonly(true);
111
                    $field->setTitle($field->Title()._t('LDAPMemberExtension.IMPORTEDFIELD', ' (imported)'));
112
                }
113
            }
114
            $message = _t(
115
                'LDAPMemberExtension.INFOIMPORTED',
116
                'This user is automatically imported from LDAP. '.
117
                    'Manual changes to imported fields will be removed upon sync.'
118
            );
119
        }
120
        if ($message) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $message of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

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