Passed
Push — master ( b2198e...e8e6de )
by Robbie
02:50 queued 10s
created

src/Tasks/LDAPMemberSyncTask.php (2 issues)

1
<?php
2
3
namespace SilverStripe\LDAP\Tasks;
4
5
use Exception;
6
use SilverStripe\Control\Director;
7
use SilverStripe\Control\HTTPRequest;
8
use SilverStripe\Core\Config\Config;
9
use SilverStripe\Dev\BuildTask;
10
use SilverStripe\LDAP\Services\LDAPService;
11
use SilverStripe\ORM\DB;
12
use SilverStripe\Security\Member;
13
14
/**
15
 * Class LDAPMemberSyncTask
16
 *
17
 * A task to sync all users from a specific DN in LDAP to the SilverStripe site, stored in Member objects
18
 */
19
class LDAPMemberSyncTask extends BuildTask
20
{
21
    /**
22
     * {@inheritDoc}
23
     * @var string
24
     */
25
    private static $segment = 'LDAPMemberSyncTask';
0 ignored issues
show
The private property $segment is not used, and could be removed.
Loading history...
26
27
    /**
28
     * @var array
29
     */
30
    private static $dependencies = [
0 ignored issues
show
The private property $dependencies is not used, and could be removed.
Loading history...
31
        'LDAPService' => '%$' . LDAPService::class,
32
    ];
33
34
    /**
35
     * Setting this to true causes the sync to delete any local Member
36
     * records that were previously imported, but no longer existing in LDAP.
37
     *
38
     * @config
39
     * @var bool
40
     */
41
    private static $destructive = false;
42
43
    /**
44
     * @var LDAPService
45
     */
46
    protected $ldapService;
47
48
    /**
49
     * @return string
50
     */
51
    public function getTitle()
52
    {
53
        return _t(__CLASS__ . '.SYNCTITLE', 'Sync all users from Active Directory');
54
    }
55
56
    /**
57
     * {@inheritDoc}
58
     * @param HTTPRequest $request
59
     */
60
    public function run($request)
61
    {
62
        ini_set('max_execution_time', 3600); // 3600s = 1hr
63
        ini_set('memory_limit', '1024M'); // 1GB memory limit
64
65
        // get all users from LDAP, but only get the attributes we need.
66
        // this is useful to avoid holding onto too much data in memory
67
        // especially in the case where getUser() would return a lot of users
68
        $users = $this->ldapService->getUsers(array_merge(
69
            ['objectguid', 'cn', 'samaccountname', 'useraccountcontrol', 'memberof'],
70
            array_keys(Config::inst()->get(Member::class, 'ldap_field_mappings'))
71
        ));
72
73
        $start = time();
74
75
        $created = 0;
76
        $updated = 0;
77
        $deleted = 0;
78
79
        foreach ($users as $data) {
80
            $member = $this->findOrCreateMember($data);
81
82
            // If member exists already, we're updating - otherwise we're creating
83
            if ($member->exists()) {
84
                $updated++;
85
                $this->log(sprintf(
86
                    'Updating existing Member %s: "%s" (ID: %s, SAM Account Name: %s)',
87
                    $data['objectguid'],
88
                    $member->getName(),
89
                    $member->ID,
90
                    $data['samaccountname']
91
                ));
92
            } else {
93
                $created++;
94
                $this->log(sprintf(
95
                    'Creating new Member %s: "%s" (SAM Account Name: %s)',
96
                    $data['objectguid'],
97
                    $data['cn'],
98
                    $data['samaccountname']
99
                ));
100
            }
101
102
            // Sync attributes from LDAP to the Member record. This will also write the Member record.
103
            // this is also responsible for putting the user into mapped groups
104
            try {
105
                $this->ldapService->updateMemberFromLDAP($member, $data);
106
            } catch (Exception $e) {
107
                $this->log($e->getMessage());
108
                continue;
109
            }
110
        }
111
112
        // remove Member records that were previously imported, but no longer exist in the directory
113
        // NOTE: DB::query() here is used for performance and so we don't run out of memory
114
        if ($this->config()->destructive) {
115
            foreach (DB::query('SELECT "ID", "GUID" FROM "Member" WHERE "GUID" IS NOT NULL') as $record) {
116
                $member = Member::get()->byId($record['ID']);
117
118
                if (!isset($users[$record['GUID']])) {
119
                    $this->log(sprintf(
120
                        'Removing Member "%s" (GUID: %s) that no longer exists in LDAP.',
121
                        $member->getName(),
122
                        $member->GUID
123
                    ));
124
125
                    try {
126
                        $member->delete();
127
                    } catch (Exception $e) {
128
                        $this->log($e->getMessage());
129
                        continue;
130
                    }
131
132
                    $deleted++;
133
                }
134
            }
135
        }
136
137
        $this->invokeWithExtensions('onAfterLDAPMemberSyncTask');
138
139
        $end = time() - $start;
140
141
        $this->log(sprintf(
142
            'Done. Created %s records. Updated %s records. Deleted %s records. Duration: %s seconds',
143
            $created,
144
            $updated,
145
            $deleted,
146
            round($end, 0)
147
        ));
148
    }
149
150
    /**
151
     * Sends a message, formatted either for the CLI or browser
152
     *
153
     * @param string $message
154
     */
155
    protected function log($message)
156
    {
157
        $message = sprintf('[%s] ', date('Y-m-d H:i:s')) . $message;
158
        echo Director::is_cli() ? ($message . PHP_EOL) : ($message . '<br>');
159
    }
160
161
    /**
162
     * Finds or creates a new {@link Member} object if the GUID provided by LDAP doesn't exist in the DB
163
     *
164
     * @param array $data The data from LDAP (specifically containing the objectguid value)
165
     * @return Member Either the existing member in the DB, or a new member object
166
     */
167
    protected function findOrCreateMember($data = [])
168
    {
169
        $member = Member::get()->filter('GUID', $data['objectguid'])->first();
170
171
        if (!($member && $member->exists())) {
172
            // create the initial Member with some internal fields
173
            $member = Member::create();
174
            $member->GUID = $data['objectguid'];
175
        }
176
177
        return $member;
178
    }
179
180
    /**
181
     * @param LDAPService $service
182
     * @return $this
183
     */
184
    public function setLDAPService(LDAPService $service)
185
    {
186
        $this->ldapService = $service;
187
        return $this;
188
    }
189
}
190