LogoutController::logout()   F
last analyzed

Complexity

Conditions 13
Paths 672

Size

Total Lines 59
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 32
c 1
b 0
f 0
dl 0
loc 59
rs 2.9055
nc 672
nop 3
cc 13

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
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\casserver\Controller;
6
7
use RuntimeException;
8
use SimpleSAML\Auth\Simple;
9
use SimpleSAML\Configuration;
10
use SimpleSAML\HTTP\RunnableResponse;
11
use SimpleSAML\Logger;
12
use SimpleSAML\Module;
13
use SimpleSAML\Module\casserver\Cas\Factories\TicketFactory;
14
use SimpleSAML\Module\casserver\Cas\ServiceValidator;
15
use SimpleSAML\Module\casserver\Cas\Ticket\TicketStore;
16
use SimpleSAML\Module\casserver\Controller\Traits\UrlTrait;
17
use SimpleSAML\Session;
18
use SimpleSAML\Utils;
19
use SimpleSAML\XHTML\Template;
20
use Symfony\Component\HttpFoundation\Request;
21
use Symfony\Component\HttpKernel\Attribute\AsController;
22
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
23
24
#[AsController]
25
class LogoutController
26
{
27
    use UrlTrait;
28
29
    /** @var \SimpleSAML\Logger */
30
    protected Logger $logger;
31
32
    /** @var \SimpleSAML\Configuration */
33
    protected Configuration $casConfig;
34
35
    /** @var \SimpleSAML\Module\casserver\Cas\Factories\TicketFactory */
36
    protected TicketFactory $ticketFactory;
37
38
    /** @var \SimpleSAML\Auth\Simple */
39
    protected Simple $authSource;
40
41
    /** @var \SimpleSAML\Utils\HTTP */
42
    protected Utils\HTTP $httpUtils;
43
44
    /** @var \SimpleSAML\Module\casserver\Cas\Ticket\TicketStore */
45
    protected TicketStore $ticketStore;
46
    private ServiceValidator $serviceValidator;
47
48
49
    /**
50
     * @param \SimpleSAML\Configuration $sspConfig
51
     * @param \SimpleSAML\Configuration|null $casConfig
52
     * @param \SimpleSAML\Auth\Simple|null $source
53
     * @param \SimpleSAML\Utils\HTTP|null $httpUtils
54
     *
55
     * @throws \Exception
56
     */
57
    public function __construct(
58
        private readonly Configuration $sspConfig,
59
        // Facilitate testing
60
        ?Configuration $casConfig = null,
61
        ?Simple $source = null,
62
        ?Utils\HTTP $httpUtils = null,
63
    ) {
64
        // We are using this work around in order to bypass Symfony's autowiring for cas configuration. Since
65
        // the configuration class is the same, it loads the ssp configuration twice. Still, we need the constructor
66
        // argument in order to facilitate testin.
67
        $this->casConfig = ($casConfig === null || $casConfig === $sspConfig)
68
            ? Configuration::getConfig('module_casserver.php') : $casConfig;
69
        $this->serviceValidator = new ServiceValidator($this->casConfig);
70
71
        $this->authSource = $source ?? new Simple($this->casConfig->getValue('authsource'));
72
        $this->httpUtils = $httpUtils ?? new Utils\HTTP();
73
74
        /* Instantiate ticket factory */
75
        $this->ticketFactory = new TicketFactory($this->casConfig);
76
77
        /* Instantiate ticket store */
78
        $ticketStoreConfig = $this->casConfig->getOptionalValue(
79
            'ticketstore',
80
            ['class' => 'casserver:FileSystemTicketStore'],
81
        );
82
        $ticketStoreClass = Module::resolveClass($ticketStoreConfig['class'], 'Cas\Ticket');
83
        $this->ticketStore = new $ticketStoreClass($this->casConfig);
84
    }
85
86
    /**
87
     * @param \Symfony\Component\HttpFoundation\Request $request
88
     * @param string|null $url
89
     *
90
     * @return \SimpleSAML\XHTML\Template|\SimpleSAML\HTTP\RunnableResponse
91
     */
92
    public function logout(
93
        Request $request,
94
        #[MapQueryParameter] ?string $url = null,
95
        #[MapQueryParameter] ?string $service = null,
96
    ): Template|RunnableResponse {
97
        if (!$this->casConfig->getOptionalValue('enable_logout', false)) {
98
            $this->handleExceptionThrown('Logout not allowed');
99
        }
100
101
        // note: casv3 says to ignore the casv2 url parameter, however deployments will see a mix of cas v2 and
102
        // cas v3 clients so we support both.  casv3 makes a query parameter optional
103
        $isCasV3 = empty($url);
104
        $url = $isCasV3 ? $service : $url;
105
106
        // Validate the return $url is valid
107
        if (!is_null($url)) {
108
            $isValidReturnUrl = !is_null($this->serviceValidator->checkServiceURL($this->sanitize($url)));
0 ignored issues
show
introduced by
The condition is_null($this->serviceVa...$this->sanitize($url))) is always true.
Loading history...
Bug introduced by
Are you sure the usage of $this->serviceValidator-...($this->sanitize($url)) targeting SimpleSAML\Module\casser...ator::checkServiceURL() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
109
            if (!$isValidReturnUrl) {
0 ignored issues
show
introduced by
The condition $isValidReturnUrl is always false.
Loading history...
110
                try {
111
                    $url = $this->httpUtils->checkURLAllowed($url);
112
                    $isValidReturnUrl = true;
113
                } catch (\Exception $e) {
114
                    Logger::info('Invalid cas logout url ' . $e->getMessage());
115
                    $isValidReturnUrl = false;
116
                }
117
            }
118
            if (!$isValidReturnUrl) {
119
                // Protocol does not define behavior if invalid logout url sent
120
                // act like no url sent and show logout page
121
                Logger::info("Invalid logout url '$url'. Ignoring");
122
                $url = null;
123
            }
124
        }
125
126
        // Skip Logout Page configuration
127
        $skipLogoutPage = !is_null($url) && ($isCasV3 || $this->casConfig->getOptionalValue('skip_logout_page', false));
128
129
130
        // Delete the ticket from the session
131
        $session = $this->getSession();
132
        if ($session !== null) {
133
            $this->ticketStore->deleteTicket($session->getSessionId());
0 ignored issues
show
Bug introduced by
It seems like $session->getSessionId() can also be of type null; however, parameter $ticketId of SimpleSAML\Module\casser...etStore::deleteTicket() 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

133
            $this->ticketStore->deleteTicket(/** @scrutinizer ignore-type */ $session->getSessionId());
Loading history...
134
        }
135
136
        if ($this->authSource->isAuthenticated()) {
137
            // Logout and come back here to handle the logout
138
            return new RunnableResponse(
139
                [$this->authSource, 'logout'],
140
                [$this->httpUtils->getSelfURL()],
141
            );
142
        } elseif ($skipLogoutPage) {
143
            $params = [];
144
            return new RunnableResponse([$this->httpUtils, 'redirectTrustedURL'], [$url, $params]);
145
        } else {
146
            $t = new Template($this->sspConfig, 'casserver:loggedOut.twig');
147
            if ($url) {
148
                $t->data['url'] = $url;
149
            }
150
            return $t;
151
        }
152
    }
153
154
    /**
155
     * @return \SimpleSAML\Module\casserver\Cas\Ticket\TicketStore
156
     */
157
    public function getTicketStore(): TicketStore
158
    {
159
        return $this->ticketStore;
160
    }
161
162
    /**
163
     * @param string $message
164
     *
165
     * @return void
166
     */
167
    protected function handleExceptionThrown(string $message): void
168
    {
169
        Logger::debug('casserver:' . $message);
170
        throw new RuntimeException($message);
171
    }
172
173
    /**
174
     * Get the Session
175
     *
176
     * @return \SimpleSAML\Session|null
177
     */
178
    protected function getSession(): ?Session
179
    {
180
        return Session::getSession();
181
    }
182
}
183