Passed
Pull Request — master (#45)
by
unknown
12:23
created

Cas20Controller::__construct()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 10
c 1
b 0
f 0
dl 0
loc 21
rs 9.9332
cc 3
nc 4
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\casserver\Controller;
6
7
use SimpleSAML\CAS\Constants as C;
8
use SimpleSAML\Configuration;
9
use SimpleSAML\Logger;
10
use SimpleSAML\Module\casserver\Cas\Factories\TicketFactory;
11
use SimpleSAML\Module\casserver\Cas\Protocol\Cas20;
12
use SimpleSAML\Module\casserver\Controller\Traits\UrlTrait;
13
use SimpleSAML\Module\casserver\Http\XmlResponse;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\Response;
16
use Symfony\Component\HttpKernel\Attribute\AsController;
17
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
18
19
#[AsController]
20
class Cas20Controller
21
{
22
    use UrlTrait;
0 ignored issues
show
introduced by
The trait SimpleSAML\Module\casser...troller\Traits\UrlTrait requires some properties which are not provided by SimpleSAML\Module\casser...troller\Cas20Controller: $request, $httpUtils, $query
Loading history...
23
24
    /** @var Logger */
25
    protected Logger $logger;
26
27
    /** @var Configuration */
28
    protected Configuration $casConfig;
29
30
    /** @var Cas20 */
31
    protected Cas20 $cas20Protocol;
32
33
    /** @var TicketFactory */
34
    protected TicketFactory $ticketFactory;
35
36
    // this could be any configured ticket store
37
    protected mixed $ticketStore;
38
39
    /**
40
     * @param   Configuration       $sspConfig
41
     * @param   Configuration|null  $casConfig
42
     * @param                       $ticketStore
43
     *
44
     * @throws \Exception
45
     */
46
    public function __construct(
47
        private readonly Configuration $sspConfig,
48
        Configuration $casConfig = null,
49
        $ticketStore = null,
50
    ) {
51
        // We are using this work around in order to bypass Symfony's autowiring for cas configuration. Since
52
        // the configuration class is the same, it loads the ssp configuration twice. Still, we need the constructor
53
        // argument in order to facilitate testing
54
        $this->casConfig = ($casConfig === null || $casConfig === $sspConfig)
55
            ? Configuration::getConfig('module_casserver.php') : $casConfig;
56
        $this->cas20Protocol = new Cas20($this->casConfig);
57
        /* Instantiate ticket factory */
58
        $this->ticketFactory = new TicketFactory($this->casConfig);
59
        /* Instantiate ticket store */
60
        $ticketStoreConfig = $this->casConfig->getOptionalValue(
61
            'ticketstore',
62
            ['class' => 'casserver:FileSystemTicketStore'],
63
        );
64
        $ticketStoreClass  = 'SimpleSAML\\Module\\casserver\\Cas\\Ticket\\'
65
            . explode(':', $ticketStoreConfig['class'])[1];
66
        $this->ticketStore = $ticketStore ?? new $ticketStoreClass($this->casConfig);
67
    }
68
69
    /**
70
     * @param   Request      $request
71
     * @param   string       $TARGET
72
     * @param   bool  $renew  [OPTIONAL] - if this parameter is set, ticket validation will only succeed
73
     *                        if the service ticket was issued from the presentation of the user’s primary
74
     *                        credentials. It will fail if the ticket was issued from a single sign-on session.
75
     * @param   string|null  $ticket  [REQUIRED] - the service ticket issued by /login
76
     * @param   string|null  $service [REQUIRED] - the identifier of the service for which the ticket was issued
77
     * @param   string|null  $pgtUrl  [OPTIONAL] - the URL of the proxy callback
78
     *
79
     * @return XmlResponse
80
     */
81
    public function serviceValidate(
82
        Request $request,
83
        #[MapQueryParameter] string $TARGET = '',
84
        #[MapQueryParameter] bool $renew = false,
85
        #[MapQueryParameter] ?string $ticket = null,
86
        #[MapQueryParameter] ?string $service = null,
87
        #[MapQueryParameter] ?string $pgtUrl = null,
88
    ): XmlResponse {
89
        return $this->validate(
90
            request: $request,
91
            method:  'serviceValidate',
92
            renew:   $renew,
93
            target:  $TARGET,
94
            ticket:  $ticket,
95
            service: $service,
96
            pgtUrl:  $pgtUrl,
97
        );
98
    }
99
100
    /**
101
     * /proxy provides proxy tickets to services that have
102
     * acquired proxy-granting tickets and will be proxying authentication to back-end services.
103
     *
104
     * @param   Request      $request
105
     * @param   string|null  $targetService [REQUIRED] - the service identifier of the back-end service.
106
     * @param   string|null  $pgt  [REQUIRED] - the proxy-granting ticket acquired by the service
107
     *                             during service ticket or proxy ticket validation.
108
     *
109
     * @return XmlResponse
110
     */
111
    public function proxy(
112
        Request $request,
113
        #[MapQueryParameter] ?string $targetService = null,
114
        #[MapQueryParameter] ?string $pgt = null,
115
    ): XmlResponse {
116
        $legal_target_service_urls = $this->casConfig->getOptionalValue('legal_target_service_urls', []);
117
        // Fail if
118
        $message = match (true) {
119
            // targetService pareameter is not defined
120
            $targetService === null => 'Missing target service parameter [targetService]',
121
            // pgt parameter is not defined
122
            $pgt === null => 'Missing proxy granting ticket parameter: [pgt]',
123
            !$this->checkServiceURL($this->sanitize($targetService), $legal_target_service_urls) =>
0 ignored issues
show
Deprecated Code introduced by
The function SimpleSAML\Module\casser...ller::checkServiceURL() has been deprecated. ( Ignorable by Annotation )

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

123
            !/** @scrutinizer ignore-deprecated */ $this->checkServiceURL($this->sanitize($targetService), $legal_target_service_urls) =>
Loading history...
Bug introduced by
It seems like $targetService can also be of type null; however, parameter $parameter of SimpleSAML\Module\casser...0Controller::sanitize() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

123
            !$this->checkServiceURL($this->sanitize(/** @scrutinizer ignore-type */ $targetService), $legal_target_service_urls) =>
Loading history...
124
                "Target service parameter not listed as a legal service: [targetService] = {$targetService}",
125
            default => null,
126
        };
127
128
        if (!empty($message)) {
129
            return new XmlResponse(
130
                (string)$this->cas20Protocol->getValidateFailureResponse(C::ERR_INVALID_REQUEST, $message),
131
                Response::HTTP_BAD_REQUEST,
132
            );
133
        }
134
135
        // Get the ticket
136
        $proxyGrantingTicket = $this->ticketStore->getTicket($pgt);
137
        $message = match (true) {
138
            // targetService parameter is not defined
139
            $proxyGrantingTicket === null => "Ticket {$pgt} not recognized",
140
            // pgt parameter is not defined
141
            !$this->ticketFactory->isProxyGrantingTicket($proxyGrantingTicket)
142
            => "Not a valid proxy granting ticket id: {$pgt}",
143
            default => null,
144
        };
145
146
        if (!empty($message)) {
147
            return new XmlResponse(
148
                (string)$this->cas20Protocol->getValidateFailureResponse('BAD_PGT', $message),
149
                Response::HTTP_BAD_REQUEST,
150
            );
151
        }
152
153
        // Get the session id from the ticket
154
        $sessionTicket = $this->ticketStore->getTicket($proxyGrantingTicket['sessionId']);
155
156
        if (
157
            $sessionTicket === null
158
            || $this->ticketFactory->isSessionTicket($sessionTicket) === false
159
            || $this->ticketFactory->isExpired($sessionTicket)
160
        ) {
161
            $message = "Ticket {$pgt} has expired";
162
            Logger::debug('casserver:' . $message);
163
164
            return new XmlResponse(
165
                (string)$this->cas20Protocol->getValidateFailureResponse('BAD_PGT', $message),
166
                Response::HTTP_BAD_REQUEST,
167
            );
168
        }
169
170
        $proxyTicket = $this->ticketFactory->createProxyTicket(
171
            [
172
                'service' => $targetService,
173
                'forceAuthn' => $proxyGrantingTicket['forceAuthn'],
174
                'attributes' => $proxyGrantingTicket['attributes'],
175
                'proxies' => $proxyGrantingTicket['proxies'],
176
                'sessionId' => $proxyGrantingTicket['sessionId'],
177
                ],
178
        );
179
180
        $this->ticketStore->addTicket($proxyTicket);
181
182
        return new XmlResponse(
183
            (string)$this->cas20Protocol->getProxySuccessResponse($proxyTicket['id']),
184
            Response::HTTP_OK,
185
        );
186
    }
187
188
    /**
189
     * @param   Request      $request
190
     * @param   string       $TARGET  // todo: this should go away???
191
     * @param   bool  $renew  [OPTIONAL] - if this parameter is set, ticket validation will only succeed
192
     *                        if the service ticket was issued from the presentation of the user’s primary
193
     *                        credentials. It will fail if the ticket was issued from a single sign-on session.
194
     * @param   string|null  $ticket  [REQUIRED] - the service ticket issued by /login
195
     * @param   string|null  $service  [REQUIRED] - the identifier of the service for which the ticket was issued
196
     * @param   string|null  $pgtUrl  [OPTIONAL] - the URL of the proxy callback
197
     * @return XmlResponse
198
     */
199
    public function proxyValidate(
200
        Request $request,
201
        #[MapQueryParameter] string $TARGET = '',
202
        #[MapQueryParameter] bool $renew = false,
203
        #[MapQueryParameter] ?string $ticket = null,
204
        #[MapQueryParameter] ?string $service = null,
205
        #[MapQueryParameter] ?string $pgtUrl = null,
206
    ): XmlResponse {
207
        return $this->validate(
208
            request: $request,
209
            method:  'proxyValidate',
210
            renew:   $renew,
211
            target:  $TARGET,
212
            ticket:  $ticket,
213
            service: $service,
214
            pgtUrl:  $pgtUrl,
215
        );
216
    }
217
}
218