Authstack SAML Service Provider : code coverage of AuthStack\SAMLServiceProvider\Model\AuthN

# Code coverage of Authstack SAML Service Provider: AuthStack\SAMLServiceProvider\Model\AuthN

## Class code coverage 57.32%

### Methods

- 100% __construct
- 0% send
- 100% receive
- 100% getAuthnRequest
- 100% getAuthnResponse
- 100% getLightSamlAuthnResponse
- 52.94% validateAuthnResponse
- 0% getAuthnResponseSignature
- 100% create
- 0% sendWith

### Source

Line | Code
--- | ---
1 |
    <?php
2 |
3 |
    namespace AuthStack\SAMLServiceProvider\Model;
4 |
5 |
    use Illuminate\Http\Request;
6 |
    use LightSaml\{
7 |
        Binding\BindingFactory,
8 |
        Helper,
9 |
        Model\XmlDSig\Signature,
10 |
        Model\XmlDSig\SignatureStringReader,
11 |
        Model\XmlDSig\SignatureXmlReader,
12 |
        SamlConstants,
13 |
        Model\Assertion\Issuer,
14 |
        Model\Protocol\AuthnRequest,
15 |
        Model\Protocol\NameIDPolicy,
16 |
        Model\Protocol\Response,
17 |
        Binding\AbstractBinding,
18 |
        Binding\HttpPostBinding,
19 |
        Binding\HttpRedirectBinding,
20 |
        Context\Profile\MessageContext
21 |
    };
22 |
    use AuthStack\SAMLServiceProvider\Exceptions\SAMLException;
23 |
24 |
25 |
    class AuthN
26 |
    {
27 |
        /**
28 |
         * @var ServiceProvider
29 |
         */
30 |
        protected $sp;
31 |
32 |
        /**
33 |
         * @var IdentityProvider
34 |
         */
35 |
        protected $idp;
36 |
37 |
        /**
38 |
         * @var AuthnRequest
39 |
         */
40 |
        protected $authn;
41 |
42 |
        /**
43 |
         * @var Response
44 |
         */
45 |
        protected $authn_response;
46 |
47 |
        /**
48 |
         * AuthN constructor.
49 |
         * @param bool $create
50 |
         */
51 |
        public function __construct(bool $create = false)
52 |
        {
53 |
            $this->sp = new ServiceProvider();
54 |
            $this->idp = new IdentityProvider($this->sp->getIdPEntityId());
55 |
56 |
            if($create)
57 |
            {
58 |
                $this->create();
59 |
            }
60 |
        }
61 |
62 |
        /**
63 |
         * @throws SAMLException
64 |
         */
65 |
        public function send()
66 |
        {
67 |
            switch($this->idp->getSSOBinding())
68 |
            {
69 |
                case SamlConstants::BINDING_SAML2_HTTP_POST:
70 |
                    $this->sendWith(new HttpPostBinding());
71 |
                    break;
72 |
73 |
                case SamlConstants::BINDING_SAML2_HTTP_REDIRECT:
74 |
                    $this->sendWith(new HttpRedirectBinding());
75 |
                    break;
76 |
77 |
                default:
78 |
                    throw new SAMLException(sprintf("Unsupported binding: ", $this->idp->getSSOBinding()), 500);
79 |
                    break;
80 |
            }
81 |
        }
82 |
83 |
        /**
84 |
         * @param Request|null $request
85 |
         * @return $this
86 |
         */
87 |
        public function receive(Request $request = null)
88 |
        {
89 |
            $request = $request ?? Request::createFromGlobals();
90 |
91 |
            $factory = new BindingFactory();
92 |
            $context = new MessageContext();
93 |
94 |
            $binding = $factory->getBindingByRequest($request);
95 |
96 |
            $binding->receive($request, $context);
97 |
98 |
            $this->authn_response = $context->getMessage();
99 |
100 |
            return $this;
101 |
        }
102 |
103 |
        /**
104 |
         * @return AuthnRequest
105 |
         */
106 |
        public function getAuthnRequest(): AuthnRequest
107 |
        {
108 |
            return $this->authn;
109 |
        }
110 |
111 |
        /**
112 |
         * @return AuthNResponse
113 |
         */
114 |
        public function getAuthnResponse(): AuthNResponse
115 |
        {
116 |
            $response = $this->getLightSamlAuthnResponse();
117 |
118 |
            return new AuthNResponse($response, $this->sp);
119 |
        }
120 |
121 |
        /**
122 |
         * @return Response
123 |
         */
124 |
        public function getLightSamlAuthnResponse(): Response
125 |
        {
126 |
            return $this->authn_response;
127 |
        }
128 |
129 |
        /**
130 |
         * @throws SAMLException
131 |
         * @return bool
132 |
         */
133 |
        public function validateAuthnResponse(): bool
134 |
        {
135 |
            $response = $this->getLightSamlAuthnResponse();
136 |
137 |
            //--------------------------------------------------------------------------------------------------------------
138 |
            // Validate IdP's signature. This is the most important of all security aspects.
139 |
            //--------------------------------------------------------------------------------------------------------------
140 |
            if($this->sp->validateSignature())
141 |
            {
142 |
                /** @var  $signature SignatureXmlReader|SignatureStringReader */
143 |
                $signature = $response->getSignature();
144 |
                $pubkey = $this->idp->getSigningPublicKey();
145 |
146 |
                try
147 |
                {
148 |
                    $signature->validate($pubkey);
149 |
                }
150 |
                catch(\Exception $e)
151 |
                {
152 |
                    throw new SAMLException($e->getMessage(), 400);
153 |
                }
154 |
            }
155 |
156 |
            //--------------------------------------------------------------------------------------------------------------
157 |
            // Validate status. AuthN status should be marked as successful
158 |
            //--------------------------------------------------------------------------------------------------------------
159 |
            if($this->sp->validateStatus())
160 |
            {
161 |
                if(!$response->getStatus()->isSuccess())
162 |
                {
163 |
                    throw new SAMLException(
164 |
                        sprintf("Invalid status. Message: %s", $response->getStatus()->getStatusMessage()),
165 |
                        400
166 |
                    );
167 |
                }
168 |
            }
169 |
170 |
            //--------------------------------------------------------------------------------------------------------------
171 |
            // Check the issuer. EntityId in the response MUST be equal to EntityId in IdentityProvider::class
172 |
            //--------------------------------------------------------------------------------------------------------------
173 |
            if($this->sp->validateIssuer())
174 |
            {
175 |
                if(!(0 === strcmp($response->getIssuer()->getValue(), $this->idp->getEntityID())))
176 |
                {
177 |
                    throw new SAMLException(
178 |
                        sprintf("Invalid issuer. Expected %s, received: %s", $this->idp->getEntityID(), $response->getIssuer()->getValue()),
179 |
                        400
180 |
                    );
181 |
                }
182 |
            }
183 |
184 |
            //--------------------------------------------------------------------------------------------------------------
185 |
            // Check whether the destination matches
186 |
            //--------------------------------------------------------------------------------------------------------------
187 |
            if($this->sp->validateDestination())
188 |
            {
189 |
                if(!(0 === strcmp($response->getDestination(), $this->sp->getACSUrl())))
190 |
                {
191 |
                    throw new SAMLException(
192 |
                        sprintf("Invalid destination. Expected %s, received: %s", $this->sp->getACSUrl(), $response->getDestination()),
193 |
                        400
194 |
                    );
195 |
                }
196 |
            }
197 |
198 |
            //--------------------------------------------------------------------------------------------------------------
199 |
            // Check for the issued at time and compare it to allowed boundaries
200 |
            //--------------------------------------------------------------------------------------------------------------
201 |
            if($this->sp->validateLifetime())
202 |
            {
203 |
                $iat = $response->getIssueInstantDateTime()->setTimezone(new \DateTimeZone('UTC'));
204 |
                $upper = $this->sp->getUpperDT();
205 |
                $lower = $this->sp->getLowerDT();
206 |
207 |
                if($iat > $upper)
208 |
                {
209 |
                    throw new SAMLException(
210 |
                        sprintf("Assertion valid in the future. Check the time synchronization between IdP and SP. Issued at: %s, checked against: %s",
211 |
                            $iat->format('c'), $upper->format('c')),
212 |
                        400);
213 |
                }
214 |
215 |
                if($iat < $lower)
216 |
                {
217 |
                    throw new SAMLException("Assertion expired. Issued at: %s, now: %s", $iat->format('c'), $lower->format('c'));
218 |
                }
219 |
            }
220 |
221 |
            return true;
222 |
        }
223 |
224 |
        /**
225 |
         * @return SignatureXmlReader|SignatureStringReader
226 |
         * @throws SAMLException
227 |
         */
228 |
        protected function getAuthnResponseSignature(): Signature
229 |
        {
230 |
            $signature = $this->getAuthnResponse()->getSignature();
231 |
232 |
            if(!($signature instanceof Signature))
233 |
            {
234 |
                throw new SAMLException("AuthN Response signature required by configuration but not present in the response", 400);
235 |
            }
236 |
237 |
            return $signature;
238 |
        }
239 |
240 |
        /**
241 |
         * @return AuthN
242 |
         */
243 |
        protected function create(): self
244 |
        {
245 |
            $authn = new AuthnRequest();
246 |
247 |
            $id = Helper::generateID();
248 |
249 |
            $authn
250 |
                ->setNameIDPolicy(new NameIDPolicy($this->sp->getNameIdFormat()))
251 |
                ->setAssertionConsumerServiceURL($this->sp->getACSUrl())
252 |
                ->setProtocolBinding($this->sp->getACSBinding())
253 |
                ->setID($id)
254 |
                ->setIssueInstant(new \DateTime("now", new \DateTimeZone("UTC")))
255 |
                ->setDestination($this->idp->getSSOUrl())
256 |
                ->setIssuer(new Issuer($this->sp->getEntityId()))
257 |
                ;
258 |
259 |
            if($this->idp->wantsSignedReguests())
260 |
            {
261 |
                $authn->setSignature($this->sp->getSignatureWriter());
262 |
            }
263 |
264 |
            $this->authn = $authn;
265 |
266 |
            return $this;
267 |
        }
268 |
269 |
        /**
270 |
         * @param AbstractBinding $binding
271 |
         */
272 |
        protected  function sendWith(AbstractBinding $binding)
273 |
        {
274 |
            $request = $this->getAuthnRequest();
275 |
276 |
            $context = new MessageContext();
277 |
278 |
            $context->setMessage($request);
279 |
280 |
            $binding->send($context)->send();
281 |
        }
282 |
    }