Passed
Push — master ( f872b6...81b6b5 )
by
unknown
04:25 queued 38s
created

AttributeExtractor::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
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
        if ($this->casconfig->getOptionalValue('attributes', true)) {
80
            $attributesToTransfer = $this->casconfig->getOptionalValue('attributes_to_transfer', []);
81
82
            if (sizeof($attributesToTransfer) > 0) {
83
                $casAttributes = [];
84
85
                foreach ($attributesToTransfer as $key) {
86
                    if (\array_key_exists($key, $attributes)) {
87
                        $casAttributes[$key] = $attributes[$key];
88
                    }
89
                }
90
            } else {
91
                $casAttributes = $attributes;
92
            }
93
        } else {
94
            $casAttributes = [];
95
        }
96
97
        return [
98
            'user' => $userName,
99
            'attributes' => $casAttributes,
100
        ];
101
    }
102
103
    /**
104
     * Run authproc filters with the processing chain
105
     * Creating the ProcessingChain require metadata.
106
     * - For the idp metadata use the OIDC issuer as the entityId (and the authprocs from the main config file)
107
     * - For the sp metadata use the client id as the entityId (and don’t set authprocs).
108
     *
109
     * @param   array  $state
110
     *
111
     * @return void
112
     * @throws Exception
113
     * @throws Error\UnserializableException
114
     * @throws \Exception
115
     */
116
    protected function runAuthProcs(array &$state): void
117
    {
118
        $filters = $this->casconfig->getOptionalArray('authproc', []);
119
        $idpMetadata = [
120
            'entityid' => $state['Source']['entityid'] ?? '',
121
            // ProcessChain needs to know the list of authproc filters we defined in module_oidc configuration
122
            'authproc' => $filters,
123
        ];
124
        $spMetadata = [
125
            'entityid' => $state['Destination']['entityid'] ?? '',
126
        ];
127
128
        // Get the ReturnTo from the state or fallback to the login page
129
        $state['ReturnURL'] = $state['ReturnTo'] ?? Module::getModuleURL('casserver/login.php');
130
        $state['Destination'] = $spMetadata;
131
        $state['Source'] = $idpMetadata;
132
133
        $this->processingChainFactory->build($state)->processState($state);
134
    }
135
136
    /**
137
     * This is a wrapper around Auth/State::loadState that facilitates testing by
138
     * hiding the static method
139
     *
140
     * @param   string  $stateId
141
     *
142
     * @return array|null
143
     * @throws NoState
144
     */
145
    public function manageState(string $stateId): ?array
146
    {
147
        if (empty($stateId)) {
148
            throw new NoState();
149
        }
150
151
        $state = $this->loadState($stateId, ProcessingChain::COMPLETED_STAGE);
152
153
        if (!empty($state['authSourceId'])) {
154
            $this->authSourceId = (string)$state['authSourceId'];
155
            unset($state['authSourceId']);
156
        }
157
158
        return $state;
159
    }
160
161
    /**
162
     * @param   string  $id
163
     * @param   string  $stage
164
     * @param   bool    $allowMissing
165
     *
166
     * @return array|null
167
     * @throws \SimpleSAML\Error\NoState
168
     */
169
    protected function loadState(string $id, string $stage, bool $allowMissing = false): ?array
170
    {
171
        return $this->authState::loadState($id, $stage, $allowMissing);
172
    }
173
}
174