Completed
Push — master ( ead9c0...764238 )
by Patrick
03:10
created

LDAPUser::uidInMemberUid()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 2
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 4
rs 10
1
<?php
2
namespace Auth;
3
4
class LDAPUser extends User
5
{
6
    use LDAPCachableObject;
7
8
    private $ldapObj;
9
    private $server;
10
11
    /**
12
     * Initialize a LDAPUser object
13
     *
14
     * @SuppressWarnings("StaticAccess")
15
     */
16
    public function __construct($data = false)
17
    {
18
        $this->server = \LDAP\LDAPServer::getInstance();
19
        $this->initialize($data);
20
    }
21
22
    private function checkChildGroup($array)
23
    {
24
        $res = false;
25
        for($i = 0; $i < $array['count']; $i++)
26
        {
27
            if(strpos($array[$i], $this->server->group_base) !== false)
28
            {
29
                $dn = explode(',', $array[$i]);
30
                $res = $this->isInGroupNamed(substr($dn[0], 3));
31
                if($res)
32
                {
33
                    return $res;
34
                }
35
            }
36
        }
37
        return $res;
38
    }
39
40
    /**
41
     * @param string $listName The name of the list to search
42
     * @param Group $group The group to search inside
43
     * @param string $dn The distringuished name to search for
44
     */
45
    private function isInListOrChild($listName, $group, $dn)
46
    {
47
        if(!isset($group[$listName]))
48
        {
49
            return false;
50
        }
51
        if(in_array($dn, $group[$listName]))
52
        {
53
            return true;
54
        }
55
        return $this->checkChildGroup($group[$listName]);
56
    }
57
58
    private function uidInMemberUid($group, $uid)
59
    {
60
        return (isset($group['memberUid']) && in_array($uid, $group['memberUid']));
61
    }
62
63
    public function isInGroupNamed($name)
64
    {
65
        $filter = new \Data\Filter('cn eq '.$name);
66
        $group = $this->server->read($this->server->group_base, $filter);
67
        if(!empty($group))
68
        {
69
            $group = $group[0];
70
            $dn  = $this->ldapObj->dn;
71
            $uid = $this->ldapObj->uid[0];
72
            $ret = $this->isInListOrChild('member', $group, $dn);
73
            if($ret === false)
74
            {
75
                $ret = $this->isInListOrChild('uniquemember', $group, $dn);
76
            }
77
            if($ret === false && $this->uidInMemberUid($group, $uid))
78
            {
79
                return true;
80
            }
81
            return $ret;
82
        }
83
        return false;
84
    }
85
86
    protected $valueDefaults = array(
87
        'o' => 'Volunteer'
88
    );
89
90
    protected $multiValueProps = array(
91
        'title',
92
        'ou',
93
        'host'
94
    );
95
96
    protected $cachedOnlyProps = array(
97
        'uid'
98
    );
99
100
    protected function getValueWithDefault($propName)
101
    {
102
        if(isset($this->valueDefaults[$propName]))
103
        {
104
            $tmp = $this->getFieldSingleValue($propName);
105
            if($tmp === false)
106
            {
107
                return $this->valueDefaults[$propName];
108
            }
109
            return $tmp;
110
        }
111
        return false;
112
    }
113
114
    protected function getMultiValueProp($propName)
115
    {
116
        if(in_array($propName, $this->multiValueProps))
117
        {
118
            $tmp = $this->getField($propName);
119
            if(isset($tmp['count']))
120
            {
121
                unset($tmp['count']);
122
            }
123
            return $tmp;
124
        }
125
        return false;
126
    }
127
128
    public function __get($propName)
129
    {
130
        $tmp = $this->getValueWithDefault($propName);
131
        if($tmp !== false)
132
        {
133
            return $tmp;
134
        }
135
        $tmp = $this->getMultiValueProp($propName);
136
        if($tmp !== false)
137
        {
138
            return $tmp;
139
        }
140
        return $this->getFieldSingleValue($propName);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getFieldSingleValue($propName); (string) is incompatible with the return type of the parent method Auth\User::__get of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
141
    }
142
143
    protected function setCachedOnlyProp($propName, $value)
144
    {
145
        if(in_array($propName, $this->cachedOnlyProps))
146
        {
147
            if(!is_object($this->ldapObj))
148
            {
149
                $this->setFieldLocal($propName, $value);
150
                return true;
151
            }
152
            throw new \Exception('Unsupported!');
153
        }
154
        return false;
155
    }
156
157
    protected function setMultiValueProp($propName, $value)
158
    {
159
        if(in_array($propName, $this->multiValueProps) && !is_array($value))
160
        {
161
             $this->setField($propName, array($value));
162
             return true;
163
        }
164
        return false;
165
    }
166
167
    public function __set($propName, $value)
168
    {
169
        if($this->setCachedOnlyProp($propName, $value) === true)
170
        {
171
            return;
172
        }
173
        if($this->setMultiValueProp($propName, $value) === true)
174
        {
175
            return;
176
        }
177
        $this->setField($propName, $value);
178
    }
179
180
    /**
181
     * Allow write for the user
182
     *
183
     * @SuppressWarnings("StaticAccess")
184
     */
185
    protected function enableReadWrite()
186
    {
187
        //Make sure we are bound in write mode
188
        $auth = \AuthProvider::getInstance();
189
        $ldap = $auth->getMethodByName('Auth\LDAPAuthenticator');
190
        if($ldap !== false)
191
        {
192
            $ldap->getAndBindServer(true);
193
        }
194
    }
195
196
    public function getGroups()
197
    {
198
        $res = array();
199
        $groups = $this->server->read($this->server->group_base);
200
        if(!empty($groups))
201
        {
202
            $count = count($groups);
203
            for($i = 0; $i < $count; $i++)
204
            {
205
                if($this->isInGroupNamed($groups[$i]['cn'][0]))
206
                {
207
                    array_push($res, new LDAPGroup($groups[$i]));
208
                }
209
            }
210
            return $res;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $res; (array) is incompatible with the return type of the parent method Auth\User::getGroups of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
211
        }
212
        return false;
213
    }
214
215
    public function addLoginProvider($provider)
216
    {
217
        return $this->appendField('host', $provider);
218
    }
219
220
    private function generateLDAPPass($pass)
221
    {
222
        mt_srand((double)microtime() * 1000000);
223
        $salt = pack("CCCC", mt_rand(), mt_rand(), mt_rand(), mt_rand());
224
        $hash = base64_encode(pack('H*', sha1($pass.$salt)).$salt);
225
        return '{SSHA}'.$hash;
226
    }
227
228
    public function setPass($password)
229
    {
230
        if(!is_object($this->ldapObj))
231
        {
232
            return $this->setFieldLocal('userPassword', $this->generateLDAPPass($password));
233
        }
234
        $obj = array('dn'=>$this->ldapObj->dn);
235
        $obj['userPassword'] = $this->generateLDAPPass($password);
236
        if(isset($this->ldapObj->uniqueidentifier))
237
        {
238
            $obj['uniqueIdentifier'] = null;
239
        }
240
        //Make sure we are bound in write mode
241
        $this->enableReadWrite();
242
        return $this->update($obj);
243
    }
244
245
    public function validate_password($password)
246
    {
247
        return $this->server->bind($this->ldapObj->dn, $password) !== false;
248
    }
249
250
    public function validate_reset_hash($hash)
251
    {
252
        if(isset($this->ldapObj->uniqueidentifier) && strcmp($this->ldapObj->uniqueidentifier[0], $hash) === 0)
253
        {
254
            return true;
255
        }
256
        return false;
257
    }
258
259
    public static function from_name($name, $data)
260
    {
261
        if($data === false)
262
        {
263
            throw new \Exception('data must be set for LDAPUser');
264
        }
265
        $filter = new \Data\Filter("uid eq $name");
266
        $user = $data->read($data->user_base, $filter);
267
        if(empty($user))
268
        {
269
            return false;
270
        }
271
        return new static($user[0]);
272
    }
273
274
    public function flushUser()
275
    {
276
        if(is_object($this->ldapObj))
277
        {
278
            //In this mode we are always up to date
279
            return true;
280
        }
281
        $obj = $this->ldapObj;
282
        $obj['objectClass'] = array('top', 'inetOrgPerson', 'extensibleObject');
283
        $obj['dn'] = 'uid='.$this->ldapObj['uid'].','.$this->server->user_base;
284
        if(!isset($obj['sn']))
285
        {
286
            $obj['sn'] = $obj['uid'];
287
        }
288
        if(!isset($obj['cn']))
289
        {
290
            $obj['cn'] = $obj['uid'];
291
        }
292
        $ret = $this->server->create($obj);
293
        return $ret;
294
    }
295
296
    private function getHashFromUser($ldapObj)
297
    {
298
        if(isset($ldapObj->userpassword))
299
        {
300
            return hash('sha512', $ldapObj->dn.';'.$ldapObj->userpassword[0].';'.$ldapObj->mail[0]);
301
        }
302
        return hash('sha512', $ldapObj->dn.';'.openssl_random_pseudo_bytes(10).';'.$ldapObj->mail[0]);
303
    }
304
305
    public function getPasswordResetHash()
306
    {
307
        //Make sure we are bound in write mode
308
        $this->enableReadWrite();
309
        $ldapObj = $this->server->read($this->server->user_base, new \Data\Filter('uid eq '.$this->uid));
310
        $ldapObj = $ldapObj[0];
311
        $hash = $this->getHashFromUser($ldapObj);
312
        $obj = array('dn'=>$this->ldapObj->dn);
313
        $obj['uniqueIdentifier'] = $hash;
314
        if($this->server->update($obj) === false)
315
        {
316
            throw new \Exception('Unable to create hash in LDAP object!');
317
        }
318
        return $hash;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $hash; (string) is incompatible with the return type of the parent method Auth\User::getPasswordResetHash of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
319
    }
320
321
    public function delete()
322
    {
323
        //Make sure we are bound in write mode
324
        $this->enableReadWrite();
325
        return $this->server->delete($this->ldapObj->dn);
326
    }
327
}
328
/* vim: set tabstop=4 shiftwidth=4 expandtab: */
329