Methods

Source

LineCode
1
<?php
2
3
namespace AuthStack\SAMLServiceProvider\Model;
4
5
use LightSaml\Helper;
6
use LightSaml\SamlConstants;
7
use Illuminate\Http\Request;
8
use LightSaml\Model\Assertion\Issuer;
9
use LightSaml\Binding\AbstractBinding;
10
use LightSaml\Binding\BindingFactory;
11
use LightSaml\Binding\HttpPostBinding;
12
use LightSaml\Binding\HttpRedirectBinding;
13
use LightSaml\Model\Protocol\LogoutRequest;
14
use LightSaml\Model\Protocol\LogoutResponse;
15
use LightSaml\Context\Profile\MessageContext;
16
use LightSaml\Model\XmlDSig\SignatureXmlReader;
17
use LightSaml\Model\XmlDSig\SignatureStringReader;
18
use AuthStack\SAMLServiceProvider\Exceptions\SAMLException;
19
use AuthStack\SAMLServiceProvider\Model\LogoutResponse as LogoutResp;
20
21
class Logout
22
{
23
    /**
24
     * @var ServiceProvider
25
     */
26
    protected $sp;
27
28
    /**
29
     * @var IdentityProvider
30
     */
31
    protected $idp;
32
33
    /**
34
     * @var LogoutRequest
35
     */
36
    protected $logout_request;
37
38
    /**
39
     * @var LogoutResponse
40
     */
41
    protected $logout_response;
42
43
    /**
44
     * Logout constructor.
45
     * @param bool $create
46
     */
47
    public function __construct(bool $create = false)
48
    {
49
        $this->sp = new ServiceProvider();
50
51
        $this->idp = new IdentityProvider($this->sp->getIdPEntityId());
52
53
        if($create)
54
        {
55
            $this->create();
56
        }
57
    }
58
59
    /**
60
     * @param Request|null $request
61
     * @return $this
62
     */
63
    public function receive(Request $request = null): self
64
    {
65
        $request = $request ?? Request::createFromGlobals();
66
67
        $factory = new BindingFactory();
68
        $context = new MessageContext();
69
70
        $binding = $factory->getBindingByRequest($request);
71
72
        $binding->receive($request, $context);
73
74
        $this->logout_request = $context->getMessage();
75
76
        return $this;
77
    }
78
79
    /**
80
     * @return LogoutRequest
81
     */
82
    public function getLogoutRequest(): LogoutRequest
83
    {
84
        return $this->logout_request;
85
    }
86
87
    /**
88
     * @return \AuthStack\SAMLServiceProvider\Model\LogoutResponse
89
     */
90
    public function getLogoutResponse()
91
    {
92
        return new LogoutResp($this->getLogoutRequest(), $this->sp, $this->idp);
93
    }
94
95
    /**
96
     * @return LogoutResponse
97
     */
98
    public function getLightSamlLogoutResponse(): LogoutResponse
99
    {
100
        return $this->logout_response;
101
    }
102
103
    /**
104
     * @return bool
105
     * @throws SAMLException
106
     */
107
    public function validateLogoutRequest(): bool
108
    {
109
        $request = $this->getLogoutRequest();
110
111
        //--------------------------------------------------------------------------------------------------------------
112
        // Validate IdP's signature. This is the most important of all security aspects.
113
        //--------------------------------------------------------------------------------------------------------------
114
        if($this->idp->wantsSignedReguests())
115
        {
116
            /** @var  $signature SignatureXmlReader|SignatureStringReader */
117
            $signature = $request->getSignature();
118
119
            $pubkey = $this->sp->getSigningPublicKey();
120
121
            try
122
            {
123
                $signature->validate($pubkey);
124
            }
125
            catch(\Exception $e)
126
            {
127
                throw new SAMLException($e->getMessage(), 400);
128
            }
129
        }
130
131
        //--------------------------------------------------------------------------------------------------------------
132
        // Check the issuer. EntityId in the response MUST be equal to EntityId in IdentityProvider::class
133
        //--------------------------------------------------------------------------------------------------------------
134
        if(!(0 === strcmp($request->getIssuer()->getValue(), $this->sp->getEntityID())))
135
        {
136
            throw new SAMLException(
137
                sprintf("Invalid issuer. Expected %s, received: %s", $this->sp->getEntityID(), $request->getIssuer()->getValue()),
138
                400
139
            );
140
        }
141
142
        //--------------------------------------------------------------------------------------------------------------
143
        // Check whether the destination matches
144
        //--------------------------------------------------------------------------------------------------------------
145
        if(!(0 === strcmp($request->getDestination(), $this->idp->getLogoutURL())))
146
        {
147
            throw new SAMLException(
148
                sprintf("Invalid destination. Expected %s, received: %s", $this->idp->getLogoutURL(), $request->getDestination()),
149
                400
150
            );
151
        }
152
153
        //--------------------------------------------------------------------------------------------------------------
154
        // Check for the issued at time and compare it to allowed boundaries
155
        //--------------------------------------------------------------------------------------------------------------
156
        $iat = $request->getIssueInstantDateTime()->setTimezone(new \DateTimeZone('UTC'));
157
        $upper = $this->idp->getUpperDT();
158
        $lower = $this->idp->getLowerDT();
159
160
        if($iat > $upper)
161
        {
162
            throw new SAMLException(
163
                sprintf("Assertion valid in the future. Check the time synchronization between IdP and SP. Issued at: %s, checked against: %s",
164
                    $iat->format('c'), $upper->format('c')),
165
                400);
166
        }
167
168
        if($iat < $lower)
169
        {
170
            throw new SAMLException("Assertion expired. Issued at: %s, now: %s", $iat->format('c'), $lower->format('c'));
171
        }
172
173
        return true;
174
    }
175
176
    /**
177
     * @throws SAMLException
178
     */
179
    public function send()
180
    {
181
        switch($this->idp->getLogoutBinding())
182
        {
183
            case SamlConstants::BINDING_SAML2_HTTP_POST:
184
                $this->sendWith(new HttpPostBinding());
185
                break;
186
187
            case SamlConstants::BINDING_SAML2_HTTP_REDIRECT:
188
                $this->sendWith(new HttpRedirectBinding());
189
                break;
190
191
            default:
192
                throw new SAMLException(sprintf("Unsupported binding: ", $this->idp->getLogoutBinding()), 500);
193
                break;
194
        }
195
    }
196
197
    /**
198
     * @return Logout
199
     */
200
    protected function create(): self
201
    {
202
        $request = new LogoutRequest();
203
        $request_id = Helper::generateID();
204
205
        $request
206
            ->setID($request_id)
207
            ->setDestination($this->idp->getLogoutURL())
208
            ->setIssueInstant(time())
209
            ->setIssuer(new Issuer($this->sp->getEntityId()))
210
        ;
211
212
        if($this->idp->wantsSignedReguests())
213
        {
214
            $request->setSignature($this->idp->getSignatureWriter());
215
        }
216
217
        $this->logout_request = $request;
218
219
        return $this;
220
    }
221
222
    /**
223
     * @param AbstractBinding $binding
224
     */
225
    protected  function sendWith(AbstractBinding $binding)
226
    {
227
        $request = $this->getLogoutRequest();
228
229
        $context = new MessageContext();
230
231
        $context->setMessage($request);
232
233
        $binding->send($context)->send();
234
    }
235
}