AttributeExtractor   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 152
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 14
eloc 47
dl 0
loc 152
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A loadState() 0 3 1
A manageState() 0 14 3
B extractUserAndAttributes() 0 37 8
A runAuthProcs() 0 18 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\casserver\Cas;
6
7
use SimpleSAML\Auth\ProcessingChain;
8
use SimpleSAML\Auth\State;
9
use SimpleSAML\Configuration;
10
use SimpleSAML\Error\NoState;
11
use SimpleSAML\Module;
12
use SimpleSAML\Module\casserver\Cas\Factories\ProcessingChainFactory;
13
14
/**
15
 * Extract the user and any mapped attributes from the AuthSource attributes
16
 */
17
class AttributeExtractor
18
{
19
    /** @var Configuration */
20
    private Configuration $casconfig;
21
22
    /** @var ProcessingChainFactory  */
23
    private ProcessingChainFactory $processingChainFactory;
24
25
    /** @var State */
26
    private State $authState;
27
28
    /**
29
     * ID of the Authentication Source used during authn.
30
     */
31
    private ?string $authSourceId = null;
32
33
    public function __construct(
34
        Configuration $casconfig,
35
        ProcessingChainFactory $processingChainFactory,
36
    ) {
37
        $this->casconfig = $casconfig;
38
        $this->processingChainFactory = $processingChainFactory;
39
        $this->authState = new State();
40
    }
41
42
    /**
43
     * Determine the user and any CAS attributes based on the attributes from the
44
     * authsource and the CAS configuration.
45
     *
46
     * The result is an array
47
     * [
48
     *   'user' => 'user_value',
49
     *   'attributes' => [
50
     *    // any attributes
51
     * ]
52
     *
53
     * If no CAS attributes are configured, then the attributes' array is empty
54
     *
55
     * @param   array|null  $state
56
     *
57
     * @return array
58
     * @throws \Exception
59
     */
60
    public function extractUserAndAttributes(?array $state): array
61
    {
62
        if (
63
            !isset($state[ProcessingChain::AUTHPARAM])
64
            && $this->casconfig->hasValue('authproc')
65
        ) {
66
            $this->runAuthProcs($state);
67
        }
68
69
        // Get the attributes from the state
70
        $attributes = $state['Attributes'];
71
72
        $casUsernameAttribute = $this->casconfig->getOptionalValue('attrname', 'eduPersonPrincipalName');
73
74
        $userName = $attributes[$casUsernameAttribute][0];
75
        if (empty($userName)) {
76
            throw new \Exception("No cas user defined for attribute $casUsernameAttribute");
77
        }
78
79
        $casAttributes = [];
80
        if ($this->casconfig->getOptionalValue('attributes', true)) {
81
            $attributesToTransfer = $this->casconfig->getOptionalValue('attributes_to_transfer', []);
82
83
            if (sizeof($attributesToTransfer) > 0) {
84
                foreach ($attributesToTransfer as $key) {
85
                    if (\array_key_exists($key, $attributes)) {
86
                        $casAttributes[$key] = $attributes[$key];
87
                    }
88
                }
89
            } else {
90
                $casAttributes = $attributes;
91
            }
92
        }
93
94
        return [
95
            'user' => $userName,
96
            'attributes' => $casAttributes,
97
        ];
98
    }
99
100
    /**
101
     * Run authproc filters with the processing chain
102
     * Creating the ProcessingChain require metadata.
103
     * - For the idp metadata use the OIDC issuer as the entityId (and the authprocs from the main config file)
104
     * - For the sp metadata use the client id as the entityId (and don’t set authprocs).
105
     *
106
     * @param   array  $state
107
     *
108
     * @return void
109
     * @throws Exception
110
     * @throws Error\UnserializableException
111
     * @throws \Exception
112
     */
113
    protected function runAuthProcs(array &$state): void
114
    {
115
        $filters = $this->casconfig->getOptionalArray('authproc', []);
116
        $idpMetadata = [
117
            'entityid' => $state['Source']['entityid'] ?? '',
118
            // ProcessChain needs to know the list of authproc filters we defined in module_oidc configuration
119
            'authproc' => $filters,
120
        ];
121
        $spMetadata = [
122
            'entityid' => $state['Destination']['entityid'] ?? '',
123
        ];
124
125
        // Get the ReturnTo from the state or fallback to the login page
126
        $state['ReturnURL'] = $state['ReturnTo'] ?? Module::getModuleURL('casserver/login.php');
127
        $state['Destination'] = $spMetadata;
128
        $state['Source'] = $idpMetadata;
129
130
        $this->processingChainFactory->build($state)->processState($state);
131
    }
132
133
    /**
134
     * This is a wrapper around Auth/State::loadState that facilitates testing by
135
     * hiding the static method
136
     *
137
     * @param   string  $stateId
138
     *
139
     * @return array|null
140
     * @throws NoState
141
     */
142
    public function manageState(string $stateId): ?array
143
    {
144
        if (empty($stateId)) {
145
            throw new NoState();
146
        }
147
148
        $state = $this->loadState($stateId, ProcessingChain::COMPLETED_STAGE);
149
150
        if (!empty($state['authSourceId'])) {
151
            $this->authSourceId = (string)$state['authSourceId'];
152
            unset($state['authSourceId']);
153
        }
154
155
        return $state;
156
    }
157
158
    /**
159
     * @param   string  $id
160
     * @param   string  $stage
161
     * @param   bool    $allowMissing
162
     *
163
     * @return array|null
164
     * @throws \SimpleSAML\Error\NoState
165
     */
166
    protected function loadState(string $id, string $stage, bool $allowMissing = false): ?array
167
    {
168
        return $this->authState::loadState($id, $stage, $allowMissing);
169
    }
170
}
171