WebAuthn::process()   B
last analyzed

Complexity

Conditions 10
Paths 13

Size

Total Lines 59
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 10
eloc 35
c 3
b 0
f 0
nc 13
nop 1
dl 0
loc 59
rs 7.6666

How to fix   Long Method    Complexity   

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
/**
4
 * FIDO2/WebAuthn Authentication Processing filter
5
 *
6
 * Filter for registering or authenticating with a FIDO2/WebAuthn token after
7
 * having authenticated with the primary authsource.
8
 *
9
 * @author Stefan Winter <[email protected]>
10
 * @package SimpleSAMLphp
11
 */
12
13
declare(strict_types=1);
14
15
namespace SimpleSAML\Module\webauthn\Auth\Process;
16
17
use SimpleSAML\Auth;
18
use SimpleSAML\Configuration;
19
use SimpleSAML\Logger;
20
use SimpleSAML\Module;
21
use SimpleSAML\Module\webauthn\WebAuthn\StateData;
22
use SimpleSAML\Module\webauthn\WebAuthn\StaticProcessHelper;
23
use SimpleSAML\Session;
24
25
class WebAuthn extends Auth\ProcessingFilter
26
{
27
    /**
28
     * @var boolean should new users be considered as enabled by default?
29
     */
30
    private bool $defaultEnabled;
31
32
    /**
33
     * @var boolean switch that determines how 'toggle' will be used, if true then value of 'toggle'
34
     *              will mean whether to trigger (true) or not (false) the webauthn authentication,
35
     *              if false then $toggle means whether to switch the value of $defaultEnabled and then use that
36
     */
37
    private bool $force;
38
39
    /**
40
     * @var string an attribute which is associated with 'force' because it determines its meaning,
41
     *              it either simply means whether to trigger webauthn authentication or switch the default settings,
42
     *              if null (was not sent as attribute) then the information from database is used
43
     */
44
    private string $toggleAttrib;
45
46
    /**
47
     * @var bool a bool that determines whether to use local database or not
48
     */
49
    private bool $useDatabase;
50
51
    /**
52
     * @var string|null AuthnContextClassRef
53
     */
54
    private ?string $authnContextClassRef = null;
55
56
    /**
57
     * An object with all the parameters that will be needed in the process
58
     *
59
     * @var \SimpleSAML\Module\webauthn\WebAuthn\StateData
60
     */
61
    private StateData $stateData;
62
63
    /**
64
     * Maximum age of second-factor authentication in authproc
65
     */
66
    private int $SecondFactorMaxAge;
67
68
69
    /**
70
     * Initialize filter.
71
     *
72
     * Validates and parses the configuration.
73
     *
74
     * @param array $config Configuration information.
75
     * @param mixed $reserved For future use.
76
     *
77
     * @throws \SimpleSAML\Error\Exception if the configuration is not valid.
78
     */
79
    public function __construct(array $config, $reserved)
80
    {
81
        parent::__construct($config, $reserved);
82
83
        $moduleConfig = Configuration::getOptionalConfig('module_webauthn.php')->toArray();
84
85
        $initialStateData = new Module\webauthn\WebAuthn\StateData();
86
        Module\webauthn\Controller\WebAuthn::loadModuleConfig($moduleConfig, $initialStateData);
87
        $this->stateData = $initialStateData;
88
89
        $this->force = $config['force'] ?? true;
90
        $this->toggleAttrib = $config['attrib_toggle'] ?? 'toggle';
91
        $this->useDatabase = $config['use_database'] ?? true;
92
        $this->defaultEnabled = $config['default_enable'] ?? false;
93
        $this->authnContextClassRef = $config['authncontextclassref'] ?? null;
94
        $this->SecondFactorMaxAge = $config['secondfactormaxage'] ?? -1;
95
96
        if (array_key_exists('use_inflow_registration', $moduleConfig['registration'])) {
97
            $this->stateData->useInflowRegistration = $moduleConfig['registration']['use_inflow_registration'];
98
        } else {
99
            $this->stateData->useInflowRegistration = true;
100
        }
101
    }
102
103
104
    /**
105
     * Process a authentication response
106
     *
107
     * This function saves the state, and redirects the user to the page where
108
     * the user can register or authenticate with his token.
109
     *
110
     * @param array &$state The state of the response.
111
     *
112
     * @return void
113
     */
114
    public function process(array &$state): void
115
    {
116
        if (!array_key_exists($this->stateData->usernameAttrib, $state['Attributes'])) {
117
            Logger::warning('webauthn: cannot determine if user needs second factor, missing attribute "' .
118
                    $this->stateData->usernameAttrib . '".');
119
            return;
120
        }
121
122
        $state['saml:AuthnContextClassRef'] = $this->authnContextClassRef ??
123
                'urn:rsa:names:tc:SAML:2.0:ac:classes:FIDO';
124
        Logger::debug('webauthn: userid: ' . $state['Attributes'][$this->stateData->usernameAttrib][0]);
125
126
        $localToggle = !empty($state['Attributes'][$this->toggleAttrib]) &&
127
            !empty($state['Attributes'][$this->toggleAttrib][0]);
128
129
        if (
130
                $this->stateData->store->is2FAEnabled(
131
                    $state['Attributes'][$this->stateData->usernameAttrib][0],
132
                    $this->defaultEnabled,
133
                    $this->useDatabase,
134
                    $localToggle,
135
                    $this->force,
136
                ) === false
137
        ) {
138
            // nothing to be done here, end authprocfilter processing
139
            return;
140
        }
141
142
        if // did we do Passwordless mode successfully before?
143
        (
144
                isset($state['Attributes']['internal:FIDO2PasswordlessAuthentication']) &&
145
                // phpcs:ignore Generic.Files.LineLength.TooLong
146
                $state['Attributes']['internal:FIDO2PasswordlessAuthentication'][0] == $state['Attributes'][$this->stateData->usernameAttrib][0]
147
        ) {
148
            // then no need to trigger a second 2-Factor via authproc
149
            // just delete the internal attribute then
150
            unset($state['Attributes']['internal:FIDO2PasswordlessAuthentication']);
151
            return;
152
        }
153
        $session = Session::getSessionFromRequest();
154
        $lastSecondFactor = $session->getData("DateTime", 'LastSuccessfulSecondFactor');
155
        if // do we need to do secondFactor in interval, or even every time?
156
           // we skip only if an interval is configured AND we did successfully authenticate,
157
           // AND are within the interval
158
        (
159
                $this->SecondFactorMaxAge >= 0 && $lastSecondFactor instanceof \DateTime
160
        ) {
161
            $interval = $lastSecondFactor->diff(new \DateTime());
162
            if ($interval->invert == 1) {
163
                throw new \Exception("We are talking to a future self. Amazing.");
164
            }
165
            // phpcs:ignore Generic.Files.LineLength.TooLong
166
            $totalAge = $interval->s + 60 * $interval->i + 3600 * $interval->h + 86400 * $interval->d + 86400 * 30 * $interval->m + 86400 * 365 * $interval->y;
167
            if ($totalAge < $this->SecondFactorMaxAge) { // we are within the interval indeed, skip calling the AuthProc
168
                return;
169
            }
170
        }
171
        StaticProcessHelper::prepareState($this->stateData, $state);
172
        StaticProcessHelper::saveStateAndRedirect($state);
173
    }
174
}
175