Completed
Push — master ( 12d056...9bd6a3 )
by Patrick
17s
created

LDAPUser::setCachedOnlyProp()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 13
rs 9.4285
1
<?php
2
namespace Auth;
3
4
class LDAPUser extends User
5
{
6
    use LDAPCachableObject;
7
8
    private $ldapObj;
9
    private $server;
10
11
    public function __construct($data = false)
12
    {
13
        $this->server = \LDAP\LDAPServer::getInstance();
14
        if($data !== false && !isset($data['dn']) && !isset($data['extended']))
15
        {
16
            //Generic user object
17
            $filter = new \Data\Filter('mail eq '.$data['mail']);
18
            $users = $this->server->read($this->server->user_base, $filter);
19
            if($users === false || !isset($users[0]))
20
            {
21
                throw new \Exception('No such LDAP User!');
22
            }
23
            $this->ldapObj = $users[0];
24
        }
25 View Code Duplication
        else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
26
        {
27
            if(isset($data['extended']))
28
            {
29
                $this->ldapObj = $data['extended'];
30
            }
31
            else
32
            {
33
                $this->ldapObj = $data;
34
            }
35
        }
36
    }
37
38
    private function check_child_group($array)
39
    {
40
        $res = false;
41
        for($i = 0; $i < $array['count']; $i++)
42
        {
43
            if(strpos($array[$i], $this->server->group_base) !== false)
44
            {
45
                $dn = explode(',', $array[$i]);
46
                $res = $this->isInGroupNamed(substr($dn[0], 3));
47
                if($res)
48
                {
49
                    return $res;
50
                }
51
            }
52
        }
53
        return $res;
54
    }
55
56
    /**
57
     * @param string $listName The name of the list to search
58
     * @param Group $group The group to search inside
59
     * @param string $dn The distringuished name to search for
60
     */
61
    private function isInListOrChild($listName, $group, $dn)
62
    {
63
        if(!isset($group[$listName]))
64
        {
65
            return false;
66
        }
67
        if(in_array($dn, $group[$listName]))
68
        {
69
            return true;
70
        }
71
        return $this->check_child_group($group[$listName]);
72
    }
73
74
    public function isInGroupNamed($name)
75
    {
76
        $filter = new \Data\Filter('cn eq '.$name);
77
        $group = $this->server->read($this->server->group_base, $filter);
78
        if(!empty($group))
79
        {
80
            $group = $group[0];
81
            $dn  = $this->ldapObj->dn;
82
            $uid = $this->ldapObj->uid[0];
83
            $ret = $this->isInListOrChild('member', $group, $dn);
84
            if($ret === false)
85
            {
86
                $ret = $this->isInListOrChild('uniquemember', $group, $dn);
87
            }
88
            if($ret === false && isset($group['memberUid']) && in_array($uid, $group['memberUid']))
89
            {
90
                return true;
91
            }
92
            return $ret;
93
        }
94
        return false;
95
    }
96
97
    protected $valueDefaults = array(
98
        'o' => 'Volunteer'
99
    );
100
101
    protected $multiValueProps = array(
102
        'title',
103
        'ou',
104
        'host'
105
    );
106
107
    protected $cachedOnlyProps = array(
108
        'uid'
109
    );
110
111
    protected function getValueWithDefault($propName)
112
    {
113
        if(isset($this->valueDefaults[$propName]))
114
        {
115
            $tmp = $this->getFieldSingleValue($propName);
116
            if($tmp === false)
117
            {
118
                return $this->valueDefaults[$propName];
119
            }
120
            return $tmp;
121
        }
122
        return false;
123
    }
124
125
    protected function getMultiValueProp($propName)
126
    {
127
        if(in_array($propName, $this->multiValueProps))
128
        {
129
            $tmp = $this->getField($propName);
130
            if(isset($tmp['count']))
131
            {
132
                unset($tmp['count']);
133
            }
134
            return $tmp;
135
        }
136
        return false;
137
    }
138
139
    public function __get($propName)
140
    {
141
        $tmp = $this->getValueWithDefault($propName);
142
        if($tmp !== false)
143
        {
144
            return $tmp;
145
        }
146
        $tmp = $this->getMultiValueProp($propName);
147
        if($tmp !== false)
148
        {
149
            return $tmp;
150
        }
151
        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...
152
    }
153
154
    protected function setCachedOnlyProp($propName, $value)
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
155
    {
156
        if(in_array($propName, $this->cachedOnlyProps))
157
        {
158
            if(!is_object($this->ldapObj))
159
            {
160
                $this->setFieldLocal('uid', $uid);
0 ignored issues
show
Bug introduced by
The variable $uid does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
161
                return true;
162
            }
163
            throw new \Exception('Unsupported!');
164
        }
165
        return false;
166
    }
167
168
    protected function setMultiValueProp($propName, $value)
169
    {
170
        if(in_array($propName, $this->multiValueProps) && !is_array($value))
171
        {
172
             $this->setField($propName, array($value));
173
             return true;
174
        }
175
        return false;
176
    }
177
178
    public function __set($propName, $value)
179
    {
180
        if($this->setCachedOnlyProp($propName, $value) === true)
181
        {
182
            return;
183
        }
184
        if($this->setMultiValueProp($propName, $value) === true)
185
        {
186
            return;
187
        }
188
        $this->setField($propName, $value);
189
    }
190
191
    public function getGroups()
192
    {
193
        $res = array();
194
        $groups = $this->server->read($this->server->group_base);
195
        if(!empty($groups))
196
        {
197
            $count = count($groups);
198
            for($i = 0; $i < $count; $i++)
199
            {
200
                if($this->isInGroupNamed($groups[$i]['cn'][0]))
201
                {
202
                    array_push($res, new LDAPGroup($groups[$i]));
203
                }
204
            }
205
            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...
206
        }
207
        else
208
        {
209
            return false;
210
        }
211
    }
212
213
    public function addLoginProvider($provider)
214
    {
215
        return $this->appendField('host', $provider);
216
    }
217
218
    private function generateLDAPPass($pass)
219
    {
220
        mt_srand((double)microtime() * 1000000);
221
        $salt = pack("CCCC", mt_rand(), mt_rand(), mt_rand(), mt_rand());
222
        $hash = base64_encode(pack('H*', sha1($pass.$salt)).$salt);
223
        return '{SSHA}'.$hash;
224
    }
225
226
    public function setPass($password)
227
    {
228
        if(!is_object($this->ldapObj))
229
        {
230
            return $this->setFieldLocal('userPassword', $this->generateLDAPPass($password));
231
        }
232
        else
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
            $auth = \AuthProvider::getInstance();
242
            $ldap = $auth->getMethodByName('Auth\LDAPAuthenticator');
243
            $ldap->get_and_bind_server(true);
244
            return $this->update($obj);
245
        }
246
    }
247
248
    public function validate_password($password)
249
    {
250
        if($this->server->bind($this->ldapObj->dn, $password))
251
        {
252
            return true;
253
        }
254
        return false;
255
    }
256
257
    public function validate_reset_hash($hash)
258
    {
259
        if(isset($this->ldapObj->uniqueidentifier) && strcmp($this->ldapObj->uniqueidentifier[0], $hash) === 0)
260
        {
261
            return true;
262
        }
263
        return false;
264
    }
265
266 View Code Duplication
    public static function from_name($name, $data = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
267
    {
268
        if($data === false)
269
        {
270
            throw new \Exception('data must be set for LDAPUser');
271
        }
272
        $filter = new \Data\Filter("uid eq $name");
273
        $user = $data->read($data->user_base, $filter);
0 ignored issues
show
Bug introduced by
The method read cannot be called on $data (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
274
        if($user === false || !isset($user[0]))
275
        {
276
            return false;
277
        }
278
        return new static($user[0]);
279
    }
280
281 View Code Duplication
    public static function from_dn($dn, $data = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282
    {
283
        if($data === false)
284
        {
285
            throw new \Exception('data must be set for LDAPUser');
286
        }
287
        $filter = new \Data\Filter("dn eq $dn");
288
        $user = $data->read($data->user_base, $filter);
0 ignored issues
show
Bug introduced by
The method read cannot be called on $data (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
289
        if($user === false || !isset($user[0]))
290
        {
291
            return false;
292
        }
293
        return new static($user[0]);
294
    }
295
296
    public function flushUser()
297
    {
298
        if(is_object($this->ldapObj))
299
        {
300
            //In this mode we are always up to date
301
            return true;
302
        }
303
        $obj = $this->ldapObj;
304
        $obj['objectClass'] = array('top', 'inetOrgPerson', 'extensibleObject');
305
        $obj['dn'] = 'uid='.$this->ldapObj['uid'].','.$this->server->user_base;
306
        if(!isset($obj['sn']))
307
        {
308
            $obj['sn'] = $obj['uid'];
309
        }
310
        if(!isset($obj['cn']))
311
        {
312
            $obj['cn'] = $obj['uid'];
313
        }
314
        $ret = $this->server->create($obj);
315
        return $ret;
316
    }
317
318
    public function getPasswordResetHash()
319
    {
320
        //Make sure we are bound in write mode
321
        $auth = \AuthProvider::getInstance();
322
        $ldap = $auth->getMethodByName('Auth\LDAPAuthenticator');
323
        $ldap->get_and_bind_server(true);
324
        $ldapObj = $this->server->read($ldap->user_base, new \Data\Filter('uid eq '.$this->uid));
0 ignored issues
show
Documentation introduced by
The property uid does not exist on object<Auth\LDAPUser>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
325
        $ldapObj = $ldapObj[0];
326
        $hash = false;
0 ignored issues
show
Unused Code introduced by
$hash is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
327
        if(isset($ldapObj->userpassword))
328
        {
329
            $hash = hash('sha512', $ldapObj->dn.';'.$ldapObj->userpassword[0].';'.$ldapObj->mail[0]);
330
        }
331
        else
332
        {
333
            $hash = hash('sha512', $ldapObj->dn.';'.openssl_random_pseudo_bytes(10).';'.$ldapObj->mail[0]);
334
        }
335
        $obj = array('dn'=>$this->ldapObj->dn);
336
        $obj['uniqueIdentifier'] = $hash;
337
        if($this->server->update($obj) === false)
338
        {
339
            throw new \Exception('Unable to create hash in LDAP object!');
340
        }
341
        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...
342
    }
343
344
    public function delete()
345
    {
346
        //Make sure we are bound in write mode
347
        $auth = \AuthProvider::getInstance();
348
        $ldap = $auth->getMethodByName('Auth\LDAPAuthenticator');
349
        $ldap->get_and_bind_server(true);
350
        return $this->server->delete($this->ldapObj->dn);
351
    }
352
}
353
/* vim: set tabstop=4 shiftwidth=4 expandtab: */
354