Passed
Pull Request — master (#5)
by Tim
01:57
created

Radius::getAttributes()   B

Complexity

Conditions 9
Paths 7

Size

Total Lines 52
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 29
c 1
b 0
f 0
nc 7
nop 1
dl 0
loc 52
rs 8.0555

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\radius\Auth\Source;
6
7
use Exception;
8
use Dapphp\Radius\Radius as RadiusClient;
9
use SimpleSAML\Assert\Assert;
10
use SimpleSAML\Configuration;
11
use SimpleSAML\Logger;
12
use SimpleSAML\Module\core\Auth\UserPassBase;
13
use SimpleSAML\Utils;
14
15
use function array_key_exists;
16
use function array_merge;
17
use function is_array;
18
use function sprintf;
19
use function strtok;
20
use function var_export;
21
22
/**
23
 * RADIUS authentication source.
24
 *
25
 * This class is based on www/auth/login-radius.php.
26
 *
27
 * @package SimpleSAMLphp
28
 */
29
class Radius extends UserPassBase
30
{
31
    public const RADIUS_USERNAME = 1;
32
    public const RADIUS_VENDOR_SPECIFIC = 26;
33
    public const RADIUS_NAS_IDENTIFIER = 32;
34
35
    /**
36
     * @var array The list of radius servers to use.
37
     */
38
    private array $servers;
39
40
    /**
41
     * @var string The hostname of the radius server.
42
     */
43
    private string $hostname;
44
45
    /**
46
     * @var int The port of the radius server.
47
     */
48
    private int $port;
49
50
    /**
51
     * @var string The secret used when communicating with the radius server.
52
     */
53
    private string $secret;
54
55
    /**
56
     * @var int The timeout for contacting the radius server.
57
     */
58
    private int $timeout;
59
60
    /**
61
     * @var string|null The realm to be added to the entered username.
62
     */
63
    private ?string $realm;
64
65
    /**
66
     * @var string|null The attribute name where the username should be stored.
67
     */
68
    private ?string $usernameAttribute = null;
69
70
    /**
71
     * @var int|null The vendor for the RADIUS attributes we are interrested in.
72
     */
73
    private ?int $vendor = null;
74
75
    /**
76
     * @var int The vendor-specific attribute for the RADIUS attributes we are
77
     *     interrested in.
78
     */
79
    private int $vendorType;
80
81
    /**
82
     * @var string|null The NAS-Identifier that should be set in Access-Request packets.
83
     */
84
    private ?string $nasIdentifier = null;
85
86
    /**
87
     * @var bool Debug modus
88
     */
89
    private bool $debug;
90
91
92
    /**
93
     * Constructor for this authentication source.
94
     *
95
     * @param array $info  Information about this authentication source.
96
     * @param array $config  Configuration.
97
     */
98
    public function __construct(array $info, array $config)
99
    {
100
        // Call the parent constructor first, as required by the interface
101
        parent::__construct($info, $config);
102
103
        // Parse configuration.
104
        $cfg = Configuration::loadFromArray(
105
            $config,
106
            'Authentication source ' . var_export($this->authId, true)
107
        );
108
109
        $this->servers = $cfg->getArray('servers');
110
        // For backwards compatibility
111
        if (empty($this->servers)) {
112
            $this->hostname = $cfg->getString('hostname');
113
            $this->port = $cfg->getOptionalIntegerRange('port', 1, 65535, 1812);
114
            $this->secret = $cfg->getString('secret');
115
            $this->servers[] = [
116
                'hostname' => $this->hostname,
117
                'port' => $this->port,
118
                'secret' => $this->secret
119
            ];
120
        }
121
        $this->debug = $cfg->getOptionalBoolean('debug', false);
122
        $this->timeout = $cfg->getOptionalInteger('timeout', 5);
123
        $this->realm = $cfg->getOptionalString('realm', null);
124
        $this->usernameAttribute = $cfg->getOptionalString('username_attribute', null);
125
        $this->nasIdentifier = $cfg->getOptionalString('nas_identifier', null);
126
127
        $this->vendor = $cfg->getOptionalInteger('attribute_vendor', null);
128
        if ($this->vendor !== null) {
129
            $this->vendorType = $cfg->getInteger('attribute_vendor_type');
130
        }
131
    }
132
133
134
    /**
135
     * Attempt to log in using the given username and password.
136
     *
137
     * @param string $username  The username the user wrote.
138
     * @param string $password  The password the user wrote.
139
     * @return array[] Associative array with the user's attributes.
140
     */
141
    protected function login(string $username, string $password): array
142
    {
143
        $radius = new RadiusClient();
144
        $response = false;
145
146
        // Try to add all radius servers, trigger a failure if no one works
147
        foreach ($this->servers as $server) {
148
            $radius->setServer($server['hostname']);
149
            $radius->setAuthenticationPort($server['port']);
150
            $radius->setSecret($server['secret']);
151
            $radius->setDebug($this->debug);
152
            $radius->setTimeout($this->timeout);
153
154
            $httpUtils = new Utils\HTTP();
155
            $radius->setNasIpAddress($httpUtils->getSelfHost());
156
157
            if ($this->nasIdentifier !== null) {
158
                $radius->setAttribute(self::RADIUS_NAS_IDENTIFIER, $this->nasIdentifier);
159
            }
160
161
            if ($this->realm !== null) {
162
                $radius->setRadiusSuffix($this->realm);
163
            }
164
            $response = $radius->accessRequest($username, $password);
165
166
            if ($response !== false) {
167
                break;
168
            }
169
        }
170
171
        if ($response === false) {
172
            throw new Exception(sprintf(
173
                'Error during radius authentication; %s (%d)',
174
                $radius->getErrorMessage(),
175
                $radius->getErrorCode()
176
            ));
177
        }
178
179
        // If we get this far, we have a valid login
180
181
        $attributes = [];
182
        if ($this->usernameAttribute !== null) {
183
            $attributes[$this->usernameAttribute] = [$username];
184
        }
185
186
        if ($this->vendor === null) {
187
            /*
188
             * We aren't interested in any vendor-specific attributes. We are
189
             * therefore done now.
190
             */
191
            return $attributes;
192
        }
193
194
        $resa = $radius->getReceivedAttributes();
195
        return array_merge($attributes, $this->getAttributes($resa));
196
    }
197
198
199
    /**
200
     * @param array $reda
201
     * @return array
202
     */
203
    private function getAttributes(array $resa)
204
    {
205
        // get AAI attribute sets.
206
        if (!is_array($resa)) {
0 ignored issues
show
introduced by
The condition is_array($resa) is always true.
Loading history...
207
            throw new Exception(sprintf(
208
                'Error getting radius attributes: %s (%d)',
209
                $radius->getErrorMessage(),
210
                $radius->getErrorCode()
211
            ));
212
        }
213
214
        $attributes = [];
215
216
        // Use the received user name
217
        if ($resa['attr'] === self::RADIUS_USERNAME && $this->usernameAttribute !== null) {
218
            $attributes[$this->usernameAttribute] = [$resa['data']];
219
            return $attributes;
220
        }
221
222
        if ($resa['attr'] !== self::RADIUS_VENDOR_SPECIFIC) {
223
            return $attributes;
224
        }
225
226
        $resv = $resa['data'];
227
        if ($resv === false) {
228
            throw new Exception(sprintf(
229
                'Error getting vendor specific attribute',
230
                $radius->getErrorMessage(),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $radius seems to be never defined.
Loading history...
231
                $radius->getErrorCode()
232
            ));
233
        }
234
235
        $vendor = $resv['vendor'];
236
        $attrv = $resv['attr'];
237
        $datav = $resv['data'];
238
239
        if ($vendor !== $this->vendor || $attrv !== $this->vendorType) {
240
            return $attributes;
241
        }
242
243
        $attrib_name = strtok($datav, '=');
244
        /** @psalm-suppress TooFewArguments */
245
        $attrib_value = strtok('=');
246
247
        // if the attribute name is already in result set, add another value
248
        if (array_key_exists($attrib_name, $attributes)) {
249
            $attributes[$attrib_name][] = $attrib_value;
250
        } else {
251
            $attributes[$attrib_name] = [$attrib_value];
252
        }
253
254
        return $attributes;
255
    }
256
}
257