Completed
Push — master ( b9ba69...95d823 )
by Chad
02:04
created

LdapRoleMapper::hasGroupWithAttributeValue()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.2
c 0
b 0
f 0
cc 4
eloc 6
nc 3
nop 3
1
<?php
2
/**
3
 * This file is part of the LdapToolsBundle package.
4
 *
5
 * (c) Chad Sikorra <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace LdapTools\Bundle\LdapToolsBundle\Security\User;
12
13
use LdapTools\Connection\LdapConnection;
14
use LdapTools\LdapManager;
15
use LdapTools\Object\LdapObjectCollection;
16
use LdapTools\Utilities\LdapUtilities;
17
18
/**
19
 * Maps LDAP groups to Symfony Roles for a LdapUserInterface user.
20
 *
21
 * @author Chad Sikorra <[email protected]>
22
 */
23
class LdapRoleMapper
24
{
25
    /**
26
     * @var LdapManager
27
     */
28
    protected $ldap;
29
30
    /**
31
     * @var array
32
     */
33
    protected $options = [
34
        'check_groups_recursively' => true,
35
        'default_role' => 'ROLE_USER',
36
        'roles' => [],
37
        'role_ldap_type' => 'group',
38
        'role_attributes' => [
39
            'guid' => 'guid',
40
            'sid' => 'sid',
41
            'name' => 'name',
42
            'members' => 'members',
43
        ],
44
    ];
45
46
    /**
47
     * @param LdapManager $ldap
48
     * @param array $options
49
     */
50
    public function __construct(LdapManager $ldap, array $options)
51
    {
52
        $this->ldap = $ldap;
53
        $this->options = array_merge($this->options, $options);
54
    }
55
56
    /**
57
     * Set the roles for the user based on LDAP group membership.
58
     *
59
     * @param LdapUserInterface $user
60
     * @return LdapUserInterface
61
     */
62
    public function setRoles(LdapUserInterface $user)
63
    {
64
        $roles = [];
65
66
        if ($this->options['default_role']) {
67
            $roles[] = $this->options['default_role'];
68
        }
69
        $groups = $this->getGroupsForUser($user);
70
71
        foreach ($this->options['roles'] as $role => $roleGroups) {
72
            if ($this->hasGroupForRoles($roleGroups, $groups)) {
73
                $roles[] = $role;
74
            }
75
        }
76
        $user->setRoles($roles);
77
78
        return $user;
79
    }
80
81
    /**
82
     * Check all of the groups that are valid for a specific role against all of the LDAP groups that the user belongs
83
     * to.
84
     *
85
     * @param array $roleGroups
86
     * @param LdapObjectCollection $ldapGroups
87
     * @return bool
88
     */
89
    protected function hasGroupForRoles(array $roleGroups, LdapObjectCollection $ldapGroups)
90
    {
91
        foreach ($roleGroups as $roleGroup) {
92
            if (LdapUtilities::isValidLdapObjectDn($roleGroup)) {
93
                $attribute = 'dn';
94
            } elseif (preg_match(LdapUtilities::MATCH_GUID, $roleGroup)) {
95
                $attribute = $this->options['role_attributes']['guid'];
96
            } elseif (preg_match(LdapUtilities::MATCH_SID, $roleGroup)) {
97
                $attribute = $this->options['role_attributes']['sid'];
98
            } else {
99
                $attribute = $this->options['role_attributes']['name'];
100
            }
101
102
            if ($this->hasGroupWithAttributeValue($ldapGroups, $attribute, $roleGroup)) {
103
                return true;
104
            }
105
        }
106
107
        return false;
108
    }
109
110
    /**
111
     * Check each LDAP group to see if any of them have an attribute with a specific value.
112
     *
113
     * @param LdapObjectCollection $groups
114
     * @param string $attribute
115
     * @param string $value
116
     * @return bool
117
     */
118
    protected function hasGroupWithAttributeValue(LdapObjectCollection $groups, $attribute, $value)
119
    {
120
        $value = strtolower($value);
121
122
        /** @var \LdapTools\Object\LdapObject $group */
123
        foreach ($groups as $group) {
124
            if ($group->has($attribute) && strtolower($group->get($attribute)) === $value) {
125
                return true;
126
            }
127
        }
128
129
        return false;
130
    }
131
132
    /**
133
     * @param LdapUserInterface $user
134
     * @return LdapObjectCollection
135
     */
136
    protected function getGroupsForUser(LdapUserInterface $user)
137
    {
138
        $select = $this->options['role_attributes'];
139
        unset($select['members']);
140
141
        $query = $this->ldap->buildLdapQuery()
142
            ->from($this->options['role_ldap_type'])
143
            ->select(array_values($select));
144
145
        /**
146
         * @todo How to support recursive group checks for all LDAP types? Need a recursive method check of sorts...
147
         */
148
        if ($this->ldap->getConnection()->getConfig()->getLdapType() === LdapConnection::TYPE_AD && $this->options['check_groups_recursively']) {
149
            $query->where($query->filter()->hasMemberRecursively($user->getLdapGuid(), $this->options['role_attributes']['members']));
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class LdapTools\Query\Builder\FilterBuilder as the method hasMemberRecursively() does only exist in the following sub-classes of LdapTools\Query\Builder\FilterBuilder: LdapTools\Query\Builder\ADFilterBuilder. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
150
        } else {
151
            $query->where([$this->options['role_attributes']['members'] => $user->getLdapGuid()]);
152
        }
153
154
        return $query->getLdapQuery()->getResult();
155
    }
156
}
157