Completed
Push — master ( 764238...d18140 )
by Patrick
03:39
created

LDAPUser   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 244
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 16
Bugs 7 Features 1
Metric Value
c 16
b 7
f 1
dl 0
loc 244
rs 8.295
wmc 42
lcom 1
cbo 6

17 Methods

Rating   Name   Duplication   Size   Complexity  
A checkChildGroup() 0 17 4
A isInListOrChild() 0 12 3
A __construct() 0 5 1
A uidInMemberUid() 0 4 2
B isInGroupNamed() 0 22 5
A enableReadWrite() 0 10 2
A getGroups() 0 18 4
A addLoginProvider() 0 4 1
A generateLDAPPass() 0 7 1
A setPass() 0 16 3
A validate_password() 0 4 1
A validate_reset_hash() 0 8 3
A from_name() 0 14 3
A flushUser() 0 21 4
A getHashFromUser() 0 8 2
A getPasswordResetHash() 0 15 2
A delete() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like LDAPUser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use LDAPUser, and based on these observations, apply Extract Interface, too.

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
    /**
101
     * Allow write for the user
102
     *
103
     * @SuppressWarnings("StaticAccess")
104
     */
105
    protected function enableReadWrite()
106
    {
107
        //Make sure we are bound in write mode
108
        $auth = \AuthProvider::getInstance();
109
        $ldap = $auth->getMethodByName('Auth\LDAPAuthenticator');
110
        if($ldap !== false)
111
        {
112
            $ldap->getAndBindServer(true);
113
        }
114
    }
115
116
    public function getGroups()
117
    {
118
        $res = array();
119
        $groups = $this->server->read($this->server->group_base);
120
        if(!empty($groups))
121
        {
122
            $count = count($groups);
123
            for($i = 0; $i < $count; $i++)
124
            {
125
                if($this->isInGroupNamed($groups[$i]['cn'][0]))
126
                {
127
                    array_push($res, new LDAPGroup($groups[$i]));
128
                }
129
            }
130
            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...
131
        }
132
        return false;
133
    }
134
135
    public function addLoginProvider($provider)
136
    {
137
        return $this->appendField('host', $provider);
138
    }
139
140
    private function generateLDAPPass($pass)
141
    {
142
        mt_srand((double)microtime() * 1000000);
143
        $salt = pack("CCCC", mt_rand(), mt_rand(), mt_rand(), mt_rand());
144
        $hash = base64_encode(pack('H*', sha1($pass.$salt)).$salt);
145
        return '{SSHA}'.$hash;
146
    }
147
148
    public function setPass($password)
149
    {
150
        if(!is_object($this->ldapObj))
151
        {
152
            return $this->setFieldLocal('userPassword', $this->generateLDAPPass($password));
153
        }
154
        $obj = array('dn'=>$this->ldapObj->dn);
155
        $obj['userPassword'] = $this->generateLDAPPass($password);
156
        if(isset($this->ldapObj->uniqueidentifier))
157
        {
158
            $obj['uniqueIdentifier'] = null;
159
        }
160
        //Make sure we are bound in write mode
161
        $this->enableReadWrite();
162
        return $this->update($obj);
163
    }
164
165
    public function validate_password($password)
166
    {
167
        return $this->server->bind($this->ldapObj->dn, $password) !== false;
168
    }
169
170
    public function validate_reset_hash($hash)
171
    {
172
        if(isset($this->ldapObj->uniqueidentifier) && strcmp($this->ldapObj->uniqueidentifier[0], $hash) === 0)
173
        {
174
            return true;
175
        }
176
        return false;
177
    }
178
179
    public static function from_name($name, $data)
180
    {
181
        if($data === false)
182
        {
183
            throw new \Exception('data must be set for LDAPUser');
184
        }
185
        $filter = new \Data\Filter("uid eq $name");
186
        $user = $data->read($data->user_base, $filter);
187
        if(empty($user))
188
        {
189
            return false;
190
        }
191
        return new static($user[0]);
192
    }
193
194
    public function flushUser()
195
    {
196
        if(is_object($this->ldapObj))
197
        {
198
            //In this mode we are always up to date
199
            return true;
200
        }
201
        $obj = $this->ldapObj;
202
        $obj['objectClass'] = array('top', 'inetOrgPerson', 'extensibleObject');
203
        $obj['dn'] = 'uid='.$this->ldapObj['uid'].','.$this->server->user_base;
204
        if(!isset($obj['sn']))
205
        {
206
            $obj['sn'] = $obj['uid'];
207
        }
208
        if(!isset($obj['cn']))
209
        {
210
            $obj['cn'] = $obj['uid'];
211
        }
212
        $ret = $this->server->create($obj);
213
        return $ret;
214
    }
215
216
    private function getHashFromUser($ldapObj)
217
    {
218
        if(isset($ldapObj->userpassword))
219
        {
220
            return hash('sha512', $ldapObj->dn.';'.$ldapObj->userpassword[0].';'.$ldapObj->mail[0]);
221
        }
222
        return hash('sha512', $ldapObj->dn.';'.openssl_random_pseudo_bytes(10).';'.$ldapObj->mail[0]);
223
    }
224
225
    public function getPasswordResetHash()
226
    {
227
        //Make sure we are bound in write mode
228
        $this->enableReadWrite();
229
        $ldapObj = $this->server->read($this->server->user_base, new \Data\Filter('uid eq '.$this->uid));
230
        $ldapObj = $ldapObj[0];
231
        $hash = $this->getHashFromUser($ldapObj);
232
        $obj = array('dn'=>$this->ldapObj->dn);
233
        $obj['uniqueIdentifier'] = $hash;
234
        if($this->server->update($obj) === false)
235
        {
236
            throw new \Exception('Unable to create hash in LDAP object!');
237
        }
238
        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...
239
    }
240
241
    public function delete()
242
    {
243
        //Make sure we are bound in write mode
244
        $this->enableReadWrite();
245
        return $this->server->delete($this->ldapObj->dn);
246
    }
247
}
248
/* vim: set tabstop=4 shiftwidth=4 expandtab: */
249