Completed
Push — master ( a51466...8daf75 )
by Raffael
02:47
created

Ldap::setOptions()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 8.5125
c 0
b 0
f 0
cc 6
eloc 14
nc 9
nop 1
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * Micro
6
 *
7
 * @author      Raffael Sahli <[email protected]>
8
 * @copyright   Copryright (c) 2012-2017 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPLv3 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Micro\Auth\Adapter\Basic;
13
14
use \Micro\Auth\Exception;
15
use \Psr\Log\LoggerInterface as Logger;
16
use \Micro\Ldap as LdapServer;
17
use \Micro\Auth\Adapter\AdapterInterface;
18
use \Micro\Auth\Adapter\AbstractAdapter;
19
20
class Ldap extends AbstractAdapter
21
{
22
    /**
23
     * Ldap
24
     *
25
     * @var LdapServer
26
     */
27
    protected $ldap;
28
29
30
    /**
31
     * LDAP DN
32
     *
33
     * @var string
34
     */
35
    protected $ldap_dn;
36
37
38
    /**
39
     * my account filter
40
     *
41
     * @var string
42
     */
43
    protected $account_filter = '(uid=%s)';
44
    
45
46
    /**
47
     * Ldap connect
48
     *
49
     * @param   Iterable $config
50
     * @param   Logger $logger
51
     * @return  void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
52
     */
53
    public function __construct(?Iterable $config, Logger $logger)
54
    {
55
        $this->logger = $logger;
0 ignored issues
show
Bug introduced by
The property logger does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
56
        $this->setOptions($config);
57
    }
58
    
59
60
    /**
61
     * Set options
62
     *
63
     * @param   Iterable $config
64
     * @return  AdapterInterface
65
     */
66
    public function setOptions(?Iterable $config=null): AdapterInterface
67
    {
68
        if ($config === null) {
69
            return $this;
70
        }
71
        
72
        foreach ($config as $option => $value) {
73
            switch ($option) {
74
                case 'ldap':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
75
                    $this->ldap = new LdapServer($value, $this->logger);
76
                break;
77
                
78
                case 'account_filter':
79
                    $this->account_filter = $value;
80
                break;
81
            }
82
        }
83
        
84
        if(!isset($config['ldap'])) {
85
            $this->ldap = new LdapServer();
0 ignored issues
show
Bug introduced by
The call to Ldap::__construct() misses some required arguments starting with $config.
Loading history...
86
        }
87
88
        return parent::setOptions($config);
89
    }
90
    
91
92
    /**
93
     * Authenticate
94
     *
95
     * @return bool
96
     */
97
    public function authenticate(): bool
98
    {
99
        if (!isset($_SERVER['HTTP_AUTHORIZATION'])) {
100
            $this->logger->debug('skip auth adapter ['.get_class($this).'], no http authorization header found', [
101
                'category' => get_class($this)
102
            ]);
103
        
104
            return false;
105
        }
106
107
        $header = $_SERVER['HTTP_AUTHORIZATION'];
108
        $parts  = explode(' ', $header);
109
        
110
        if ($parts[0] == 'Basic') {
111
            $this->logger->debug('found http basic authorization header', [
112
                'category' => get_class($this)
113
            ]);
114
115
            $username = $_SERVER['PHP_AUTH_USER'];
116
            $password = $_SERVER['PHP_AUTH_PW'];
117
118
            return $this->plainAuth($username, $password);
119
        } else {
120
            $this->logger->warning('http authorization header contains no basic string or invalid authentication string', [
121
                'category' => get_class($this)
122
            ]);
123
        
124
            return false;
125
        }
126
    }
127
128
129
    /**
130
     * LDAP Auth
131
     *
132
     * @param   string $username
133
     * @param   string $password
134
     * @return  bool
135
     */
136
    protected function plainAuth(string $username, string $password): bool
137
    {
138
        $this->ldap->connect();
139
        $resource = $this->ldap->getResource();
140
141
        $esc_username = ldap_escape($username);
142
        $filter       = htmlspecialchars_decode(sprintf($this->account_filter, $esc_username));
143
        $result       = ldap_search($resource, $this->ldap->getBase(), $filter, ['dn']);
144
        $entries      = ldap_get_entries($resource, $result);
145
146
        if ($entries['count'] === 0) {
147
            $this->logger->warning("user not found with ldap filter [{$filter}]", [
148
                'category' => get_class($this)
149
            ]);
150
151
            return false;
152
        } elseif ($entries['count'] > 1) {
153
            $this->logger->warning("more than one user found with ldap filter [{$filter}]", [
154
                'category' => get_class($this)
155
            ]);
156
157
            return false;
158
        }
159
160
        $dn = $entries[0]['dn'];
161
        $this->logger->info("found ldap user [{$dn}] with filter [{$filter}]", [
162
            'category' => get_class($this)
163
        ]);
164
165
        $result = ldap_bind($resource, $dn, $password);
166
        $this->logger->info("bind ldap user [{$dn}]", [
167
            'category' => get_class($this),
168
            'result'   => $result
169
        ]);
170
171
        if ($result === false) {
172
            return false;
173
        }
174
175
        $this->identifier  = $username;
176
        $this->ldap_dn     = $dn;
177
178
        return true;
179
    }
180
181
    
182
    /**
183
     * Get attributes
184
     *
185
     * @return array
186
     */
187
    public function getAttributes(): array
188
    {
189
        $search = [];
190
        foreach ($this->map as $attr => $value) {
191
            $search[] = $value['attr'];
192
        }
193
194
        $result     = ldap_read($this->ldap->getResource(), $this->ldap_dn, '(objectClass=*)', $search);
195
        $entries    = ldap_get_entries($this->ldap->getResource(), $result);
196
        $attributes = $entries[0];
197
198
        $this->logger->info("get ldap user [{$this->ldap_dn}] attributes", [
199
            'category' => get_class($this),
200
            'params'   => $attributes,
201
        ]);
202
203
        return $attributes;
204
    }
205
}
206