Completed
Pull Request — master (#64)
by Sean
02:46
created

LDAPMemberExtension   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 194
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 9
Bugs 0 Features 0
Metric Value
wmc 20
c 9
b 0
f 0
lcom 1
cbo 8
dl 0
loc 194
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
B updateCMSFields() 0 47 5
A validate() 0 21 4
B onBeforeWrite() 0 14 5
A onAfterWrite() 0 14 4
A memberLoggedIn() 0 6 2
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
            // Transform the automatically mapped fields into read-only. This doesn't
98
            // apply if updating LDAP from local is enabled, as changing data locally can be written back.
99
            foreach ($this->owner->config()->ldap_field_mappings as $name) {
100
                $field = $fields->dataFieldByName($name);
101
                if (!empty($field)) {
102
                    // Set to readonly, but not disabled so that the data is still sent to the
103
                    // server and doesn't break Member_Validator
104
                    $field->setReadonly(true);
105
                    $field->setTitle($field->Title()._t('LDAPMemberExtension.IMPORTEDFIELD', ' (imported)'));
106
                }
107
            }
108
109
            $message = _t(
110
                'LDAPMemberExtension.INFOIMPORTED',
111
                'This user is automatically imported from LDAP. '.
112
                    'Manual changes to imported fields will be removed upon sync.'
113
            );
114
            $fields->addFieldToTab(
115
                'Root.Main',
116
                new LiteralField(
117
                    'Info',
118
                    sprintf('<p class="message warning">%s</p>', $message)
119
                ),
120
                'FirstName'
121
            );
122
        }
123
    }
124
125
    public function validate(ValidationResult $validationResult)
126
    {
127
        if (!$this->owner->config()->create_new_users_in_ldap) {
128
            return;
129
        }
130
131
        // We allow empty Username for registration purposes, as we need to
132
        // create Member records with empty Username temporarily. Forms should explicitly
133
        // check for Username not being empty if they require it not to be.
134
        if (empty($this->owner->Username)) {
135
            return;
136
        }
137
138
        if (!preg_match('/^[a-z0-9\.]+$/', $this->owner->Username)) {
139
            $validationResult->error(
140
                'Username must only contain lowercase alphanumeric characters and dots.',
141
                'bad'
142
            );
143
            throw new ValidationException($validationResult);
144
        }
145
    }
146
147
    /**
148
     * Create the user in LDAP, provided this configuration is enabled
149
     * and a username was passed to a new Member record.
150
     *
151
     * Set a flag "Creating" so other extensions using on*() events can
152
     * detect whether it's in a state of being created, such as for
153
     * synchronising with other services when a user is being created
154
     * in LDAP for the first time.
155
     */
156
    public function onBeforeWrite()
157
    {
158
        $service = Injector::inst()->get('LDAPService');
159
        if (!$service->enabled()) {
160
            return;
161
        }
162
        if (!$this->owner->config()->create_new_users_in_ldap) {
163
            return;
164
        }
165
        if ($this->owner->Username && !$this->owner->IsImportedFromLDAP) {
166
            $service->createLDAPUser($this->owner);
167
            $this->owner->Creating = true;
168
        }
169
    }
170
171
    /**
172
     * Update the local data with LDAP, and ensure local membership is also set in
173
     * LDAP too. This writes into LDAP, provided that feature is enabled.
174
     */
175
    public function onAfterWrite()
176
    {
177
        $service = Injector::inst()->get('LDAPService');
178
        if (!$service->enabled()) {
179
            return;
180
        }
181
        if (!$this->owner->config()->update_ldap_from_local) {
182
            return;
183
        }
184
        if ($this->owner->IsImportedFromLDAP) {
185
            $service->updateLDAPFromMember($this->owner);
186
            $service->updateLDAPGroupsForMember($this->owner);
187
        }
188
    }
189
190
    /**
191
     * Triggered by {@link Member::logIn()} when successfully logged in,
192
     * this will update the Member record from AD data.
193
     */
194
    public function memberLoggedIn()
195
    {
196
        if ($this->owner->GUID) {
197
            Injector::inst()->get('LDAPService')->updateMemberFromLDAP($this->owner);
198
        }
199
    }
200
}
201