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

Radius::getAttributes()   B

Complexity

Conditions 9
Paths 4

Size

Total Lines 51
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 4
nop 1
dl 0
loc 51
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 is_array;
17
use function sprintf;
18
use function strtok;
19
use function var_export;
20
21
/**
22
 * RADIUS authentication source.
23
 *
24
 * This class is based on www/auth/login-radius.php.
25
 *
26
 * @package SimpleSAMLphp
27
 */
28
class Radius extends UserPassBase
29
{
30
    public const RADIUS_USERNAME = 1;
31
    public const RADIUS_VENDOR_SPECIFIC = 26;
32
    public const RADIUS_NAS_IDENTIFIER = 32;
33
34
    /**
35
     * @var array The list of radius servers to use.
36
     */
37
    private array $servers;
38
39
    /**
40
     * @var string The hostname of the radius server.
41
     */
42
    private string $hostname;
43
44
    /**
45
     * @var int The port of the radius server.
46
     */
47
    private int $port;
48
49
    /**
50
     * @var string The secret used when communicating with the radius server.
51
     */
52
    private string $secret;
53
54
    /**
55
     * @var int The timeout for contacting the radius server.
56
     */
57
    private int $timeout;
58
59
    /**
60
     * @var string|null The realm to be added to the entered username.
61
     */
62
    private ?string $realm;
63
64
    /**
65
     * @var string|null The attribute name where the username should be stored.
66
     */
67
    private ?string $usernameAttribute = null;
68
69
    /**
70
     * @var int|null The vendor for the RADIUS attributes we are interrested in.
71
     */
72
    private ?int $vendor = null;
73
74
    /**
75
     * @var int The vendor-specific attribute for the RADIUS attributes we are
76
     *     interrested in.
77
     */
78
    private int $vendorType;
79
80
    /**
81
     * @var string|null The NAS-Identifier that should be set in Access-Request packets.
82
     */
83
    private ?string $nasIdentifier = null;
84
85
    /**
86
     * @var bool Debug modus
87
     */
88
    private bool $debug;
89
90
91
    /**
92
     * Constructor for this authentication source.
93
     *
94
     * @param array $info  Information about this authentication source.
95
     * @param array $config  Configuration.
96
     */
97
    public function __construct(array $info, array $config)
98
    {
99
        // Call the parent constructor first, as required by the interface
100
        parent::__construct($info, $config);
101
102
        // Parse configuration.
103
        $cfg = Configuration::loadFromArray(
104
            $config,
105
            'Authentication source ' . var_export($this->authId, true)
106
        );
107
108
        $this->servers = $cfg->getArray('servers');
109
        // For backwards compatibility
110
        if (empty($this->servers)) {
111
            $this->hostname = $cfg->getString('hostname');
112
            $this->port = $cfg->getOptionalIntegerRange('port', 1, 65535, 1812);
113
            $this->secret = $cfg->getString('secret');
114
            $this->servers[] = [
115
                'hostname' => $this->hostname,
116
                'port' => $this->port,
117
                'secret' => $this->secret
118
            ];
119
        }
120
        $this->debug = $cfg->getOptionalBoolean('debug', false);
121
        $this->timeout = $cfg->getOptionalInteger('timeout', 5);
122
        $this->realm = $cfg->getOptionalString('realm', null);
123
        $this->usernameAttribute = $cfg->getOptionalString('username_attribute', null);
124
        $this->nasIdentifier = $cfg->getOptionalString('nas_identifier', null);
125
126
        $this->vendor = $cfg->getOptionalInteger('attribute_vendor', null);
127
        if ($this->vendor !== null) {
128
            $this->vendorType = $cfg->getInteger('attribute_vendor_type');
129
        }
130
    }
131
132
133
    /**
134
     * Attempt to log in using the given username and password.
135
     *
136
     * @param string $username  The username the user wrote.
137
     * @param string $password  The password the user wrote.
138
     * @return array[] Associative array with the user's attributes.
139
     */
140
    protected function login(string $username, string $password): array
141
    {
142
        $radius = new RadiusClient();
143
        $response = false;
144
145
        // Try to add all radius servers, trigger a failure if no one works
146
        foreach ($this->servers as $server) {
147
            $radius->setServer($server['hostname']);
148
            $radius->setAuthenticationPort($server['port']);
149
            $radius->setSecret($server['secret']);
150
            $radius->setDebug($this->debug);
151
            $radius->setTimeout($this->timeout);
152
153
            $httpUtils = new Utils\HTTP();
154
            $radius->setNasIpAddress($httpUtils->getSelfHost());
155
156
            if ($this->nasIdentifier !== null) {
157
                $radius->setAttribute(self::RADIUS_NAS_IDENTIFIER, $this->nasIdentifier);
158
            }
159
160
            if ($this->realm !== null) {
161
                $radius->setRadiusSuffix($this->realm);
162
            }
163
            $response = $radius->accessRequest($username, $password);
164
165
            if ($response !== false) {
166
                break;
167
            }
168
        }
169
170
        if ($response === false) {
171
            throw new Exception(sprintf(
172
                'Error during radius authentication; %s (%d)',
173
                $radius->getErrorMessage(),
174
                $radius->getErrorCode()
175
            ));
176
        }
177
178
        // If we get this far, we have a valid login
179
180
        $attributes = [];
181
        $usernameAttribute = $this->usernameAttribute;
182
183
        if ($usernameAttribute !== null) {
184
            $attributes[$usernameAttribute] = [$username];
185
        }
186
187
        if ($this->vendor === null) {
188
            /*
189
             * We aren't interested in any vendor-specific attributes. We are
190
             * therefore done now.
191
             */
192
            return $attributes;
193
        }
194
195
        $resa = $radius->getReceivedAttributes();
196
        return $this->getAttributes($resa);
197
    }
198
199
200
    /**
201
     * @param array $reda
202
     * @return array
203
     */
204
    private function getAttributes(array $resa)
0 ignored issues
show
Unused Code introduced by
The parameter $resa is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

204
    private function getAttributes(/** @scrutinizer ignore-unused */ array $resa)

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

Loading history...
205
    {
206
        // get AAI attribute sets.
207
        $resa = $radius->getReceivedAttributes();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $radius seems to be never defined.
Loading history...
208
        if (!is_array($resa)) {
209
            throw new Exception(sprintf(
210
                'Error getting radius attributes: %s (%d)',
211
                $radius->getErrorMessage(),
212
                $radius->getErrorCode()
213
            ));
214
        }
215
216
        // Use the received user name
217
        if ($resa['attr'] === self::RADIUS_USERNAME && $usernameAttribute !== null) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $usernameAttribute seems to be never defined.
Loading history...
218
            $attributes[$usernameAttribute] = [$resa['data']];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$attributes was never initialized. Although not strictly required by PHP, it is generally a good practice to add $attributes = array(); before regardless.
Loading history...
219
            continue;
220
        }
221
222
        if ($resa['attr'] !== self::RADIUS_VENDOR_SPECIFIC) {
223
            continue;
224
        }
225
226
        $resv = $resa['data'];
227
        if ($resv === false) {
228
            throw new Exception(sprintf(
229
                'Error getting vendor specific attribute',
230
                $radius->getErrorMessage(),
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
            continue;
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)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $attributes seems to be never defined.
Loading history...
249
            $attributes[$attrib_name][] = $attrib_value;
250
        } else {
251
            $attributes[$attrib_name] = [$attrib_value];
252
        }
253
254
        return $attributes;
255
    }
256
}
257