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

LDAPMemberExtension::onBeforeWrite()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 20
rs 8.8571
cc 6
eloc 11
nc 6
nop 0
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 users have their data written back to LDAP.
45
     * This is a push to LDAP, rather than {@link LDAPMemberSyncTask}
46
     * which pulls from LDAP instead.
47
     *
48
     * This requires setting write permissions on the user who talks to LDAP,
49
     * which is why it's disabled by default.
50
     *
51
     * Note that some constants must be configured in your environment file
52
     * for this to work:
53
     *
54
     * LDAP_DOMAIN - the base DN of the directory. e.g. "DN=mydomain,DC=com"
55
     * LDAP_NEW_USERS_OBJECT_CATEGORY - the type of object. e.g. "CN=Person,CN=Schema,DC=mydomain,DC=com"
56
     * LDAP_NEW_USERS_DN - where to place users in the directory. e.g. "OU=Users,DC=mydomain,DC=com"
57
     *
58
     * @var bool
59
     * @config
60
     */
61
    private static $reverse_sync_ldap = false;
0 ignored issues
show
Unused Code introduced by
The property $reverse_sync_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...
62
63
    /**
64
     * If enabled, new users written are also created in LDAP.
65
     *
66
     * This requires setting write permissions on the user who talks to LDAP,
67
     * which is why it's disabled by default.
68
     *
69
     * Please see {@link $reverse_sync_ldap} for constants that must be configured in
70
     * your environment file for this to work.
71
     *
72
     * @var bool
73
     * @config
74
     */
75
    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...
76
77
    /**
78
     * @var array
79
     */
80
    private static $dependencies = array(
0 ignored issues
show
Unused Code introduced by
The property $dependencies 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...
81
        'ldapService' => '%$LDAPService',
82
    );
83
84
    /**
85
     * @param FieldList $fields
86
     */
87
    public function updateCMSFields(FieldList $fields)
88
    {
89
        // Redo LDAP metadata fields as read-only and move to LDAP tab.
90
        $ldapMetadata = array();
91
        $fields->replaceField('IsImportedFromLDAP', $ldapMetadata[] = new ReadonlyField(
92
            'IsImportedFromLDAP',
93
            _t('LDAPMemberExtension.ISIMPORTEDFROMLDAP', 'Is user imported from LDAP/AD?')
94
        ));
95
        $fields->replaceField('GUID', $ldapMetadata[] = new ReadonlyField('GUID'));
96
        $fields->replaceField('IsExpired', $ldapMetadata[] = new ReadonlyField(
97
            'IsExpired',
98
            _t('LDAPMemberExtension.ISEXPIRED', 'Has user\'s LDAP/AD login expired?'))
99
        );
100
        $fields->replaceField('LastSynced', $ldapMetadata[] = new ReadonlyField(
101
            'LastSynced',
102
            _t('LDAPMemberExtension.LASTSYNCED', 'Last synced'))
103
        );
104
        $fields->addFieldsToTab('Root.LDAP', $ldapMetadata);
105
106
        if ($this->owner->IsImportedFromLDAP && !$this->config()->reverse_sync_ldap) {
0 ignored issues
show
Bug introduced by
The method config() does not exist on LDAPMemberExtension. Did you maybe mean get_extra_config()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
107
            // Transform the automatically mapped fields into read-only. This doesn't
108
            // apply if reverse sync is enabled, as changing data locally can be written back.
109
            foreach ($this->owner->config()->ldap_field_mappings as $name) {
110
                $field = $fields->dataFieldByName($name);
111
                if (!empty($field)) {
112
                    // Set to readonly, but not disabled so that the data is still sent to the
113
                    // server and doesn't break Member_Validator
114
                    $field->setReadonly(true);
115
                    $field->setTitle($field->Title()._t('LDAPMemberExtension.IMPORTEDFIELD', ' (imported)'));
116
                }
117
            }
118
119
            // Display alert message at the top. This is not applicable if reverse sync is
120
            $message = _t(
121
                'LDAPMemberExtension.INFOIMPORTED',
122
                'This user is automatically imported from LDAP. '.
123
                    'Manual changes to imported fields will be removed upon sync.'
124
            );
125
            $fields->addFieldToTab(
126
                'Root.Main',
127
                new LiteralField(
128
                    'Info',
129
                    sprintf('<p class="message warning">%s</p>', $message)
130
                ),
131
                'FirstName'
132
            );
133
        }
134
    }
135
136
    public function validate(ValidationResult $validationResult)
137
    {
138
        if (!$this->owner->config()->create_new_users_in_ldap) {
139
            return;
140
        }
141
142
        // We allow empty Username for registration purposes, as we need to
143
        // create Member records with empty Username temporarily. Forms should explicitly
144
        // check for Username not being empty if they require it not to be.
145
        if (empty($this->owner->Username)) {
146
            return;
147
        }
148
149
        if (!preg_match('/^[a-z0-9\.]+$/', $this->owner->Username)) {
150
            $validationResult->error(
151
                'Username must only contain lowercase alphanumeric characters and dots.',
152
                'bad'
153
            );
154
            throw new ValidationException($validationResult);
155
        }
156
    }
157
158
    /**
159
     * Create the user in LDAP, provided this configuration is enabled.
160
     *
161
     * Set a flag "Creating" so other extensions using on*() events can
162
     * detect whether it's in a state of being created, such as for
163
     * synchronising with other services when a user is being created
164
     * in LDAP for the first time.
165
     */
166
    public function onBeforeWrite()
167
    {
168
        if (!$this->owner->config()->create_new_users_in_ldap) {
169
            return;
170
        }
171
        $service = $this->ldapService;
0 ignored issues
show
Bug introduced by
The property ldapService does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
172
        if (!$service->enabled()) {
173
            return;
174
        }
175
176
        if ($this->owner->Username) {
177
            $this->owner->Username = strtolower($this->owner->Username);
178
        }
179
180
        // Ensure the user exists LDAP.
181
        if ($this->owner->Username && !$this->owner->IsImportedFromLDAP) {
182
            $service->createLDAPUser($this->owner);
183
            $this->owner->Creating = true;
184
        }
185
    }
186
187
    /**
188
     * Sync the local data with LDAP, and ensure local membership is also set in
189
     * LDAP too. This writes into LDAP, provided reverse sync is enabled.
190
     */
191
    public function onAfterWrite()
192
    {
193
        if (!$this->owner->config()->reverse_sync_ldap) {
194
            return;
195
        }
196
        if ($this->owner->IsImportedFromLDAP) {
197
            $this->service->updateLDAPFromMember($this->owner);
0 ignored issues
show
Bug introduced by
The property service does not seem to exist. Did you mean ldapService?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
198
            $this->service->updateLDAPGroupsForMember($this->owner);
0 ignored issues
show
Bug introduced by
The property service does not seem to exist. Did you mean ldapService?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
199
        }
200
201
        if (
202
            !$this->owner->Creating ||
203
            !$this->owner->IsImportedFromLDAP
204
        ) {
205
            return;
206
        }
207
208
        // muzdowski: Creation is entering the last phase. I have seen framework trying to ->write
209
        // twice in a row, resulting in an irrelevant error second time around. Mark creation
210
        // explicitly as done to prevent that.
211
        $this->owner->Creating = false;
212
    }
213
214
    /**
215
     * Triggered by {@link Member::logIn()} when successfully logged in,
216
     * this will update the Member record from AD data.
217
     */
218
    public function memberLoggedIn()
219
    {
220
        if ($this->owner->GUID) {
221
            $this->ldapService->updateMemberFromLDAP($this->owner);
222
        }
223
    }
224
}
225