@@ -123,14 +123,37 @@ public function send(AbstractMessage $message): ResponseInterface
123123 */
124124 public function receive (ServerRequestInterface $ request ): AbstractMessage
125125 {
126- $ query = $ request ->getQueryParams ();
127-
128- if (array_key_exists ('SAMLRequest ' , $ query )) {
129- $ message = $ query ['SAMLRequest ' ];
130- $ signedQuery = 'SAMLRequest= ' . urlencode ($ query ['SAMLRequest ' ]);
131- } elseif (array_key_exists ('SAMLResponse ' , $ query )) {
132- $ message = $ query ['SAMLResponse ' ];
133- $ signedQuery = 'SAMLResponse= ' . urlencode ($ query ['SAMLResponse ' ]);
126+ $ query = $ this ->parseQuery ();
127+ $ signedQuery = $ query ['SignedQuery ' ];
128+
129+ /**
130+ * Get the SAMLRequest/SAMLResponse from the exact same signed data that will be verified later in
131+ * validateSignature into $res using the actual SignedQuery
132+ */
133+ $ res = [];
134+ foreach (explode ('& ' , $ signedQuery ) as $ e ) {
135+ $ tmp = explode ('= ' , $ e , 2 );
136+ $ name = $ tmp [0 ];
137+ if (count ($ tmp ) === 2 ) {
138+ $ value = $ tmp [1 ];
139+ } else {
140+ /* No value for this parameter. */
141+ $ value = '' ;
142+ }
143+ $ name = urldecode ($ name );
144+ $ res [$ name ] = urldecode ($ value );
145+ }
146+
147+ /**
148+ * Put the SAMLRequest/SAMLResponse from the actual query string into $message,
149+ * and assert that the result from parseQuery() in $query and the parsing of the SignedQuery in $res agree
150+ */
151+ if (array_key_exists ('SAMLRequest ' , $ res )) {
152+ Assert::same ($ res ['SAMLRequest ' ], $ query ['SAMLRequest ' ], 'Parse failure. ' );
153+ $ message = $ res ['SAMLRequest ' ];
154+ } elseif (array_key_exists ('SAMLResponse ' , $ res )) {
155+ Assert::same ($ res ['SAMLResponse ' ], $ query ['SAMLResponse ' ], 'Parse failure. ' );
156+ $ message = $ res ['SAMLResponse ' ];
134157 } else {
135158 throw new Exception ('Missing SAMLRequest or SAMLResponse parameter. ' );
136159 }
@@ -154,7 +177,6 @@ public function receive(ServerRequestInterface $request): AbstractMessage
154177 $ message = MessageFactory::fromXML ($ document ->documentElement );
155178
156179 if (array_key_exists ('RelayState ' , $ query )) {
157- $ signedQuery .= '&RelayState= ' . urlencode ($ query ['RelayState ' ]);
158180 $ this ->setRelayState ($ query ['RelayState ' ]);
159181 }
160182
@@ -174,8 +196,6 @@ public function receive(ServerRequestInterface $request): AbstractMessage
174196
175197 if (!array_key_exists ('SigAlg ' , $ query )) {
176198 throw new Exception ('Missing signature algorithm. ' );
177- } else {
178- $ signedQuery .= '&SigAlg= ' . urlencode ($ query ['SigAlg ' ]);
179199 }
180200
181201 $ container = ContainerSingleton::getInstance ();
@@ -192,4 +212,67 @@ public function receive(ServerRequestInterface $request): AbstractMessage
192212
193213 return $ message ;
194214 }
215+
216+
217+ /**
218+ * Helper function to parse query data.
219+ *
220+ * This function returns the query string split into key=>value pairs.
221+ * It also adds a new parameter, SignedQuery, which contains the data that is
222+ * signed.
223+ *
224+ * @return array The query data that is signed.
225+ * @throws \Exception
226+ */
227+ private static function parseQuery () : array
228+ {
229+ /*
230+ * Parse the query string. We need to do this ourself, so that we get access
231+ * to the raw (urlencoded) values. This is required because different software
232+ * can urlencode to different values.
233+ */
234+ $ data = [];
235+ $ relayState = '' ;
236+ $ sigAlg = '' ;
237+ $ sigQuery = '' ;
238+
239+ foreach (explode ('& ' , $ _SERVER ['QUERY_STRING ' ]) as $ e ) {
240+ $ tmp = explode ('= ' , $ e , 2 );
241+ $ name = $ tmp [0 ];
242+ if (count ($ tmp ) === 2 ) {
243+ $ value = $ tmp [1 ];
244+ } else {
245+ /* No value for this parameter. */
246+ $ value = '' ;
247+ }
248+
249+ $ name = urldecode ($ name );
250+ // Prevent keys from being set more than once
251+ if (array_key_exists ($ name , $ data )) {
252+ throw new Exception ('Duplicate parameter. ' );
253+ }
254+ $ data [$ name ] = urldecode ($ value );
255+
256+ switch ($ name ) {
257+ case 'SAMLRequest ' :
258+ case 'SAMLResponse ' :
259+ $ sigQuery = $ name . '= ' . $ value ;
260+ break ;
261+ case 'RelayState ' :
262+ $ relayState = '&RelayState= ' . $ value ;
263+ break ;
264+ case 'SigAlg ' :
265+ $ sigAlg = '&SigAlg= ' . $ value ;
266+ break ;
267+ }
268+ }
269+
270+ if (array_key_exists ('SAMLRequest ' , $ data ) && array_key_exists ('SAMLResponse ' , $ data )) {
271+ throw new Exception ('Both SAMLRequest and SAMLResponse provided. ' );
272+ }
273+
274+ $ data ['SignedQuery ' ] = $ sigQuery . $ relayState . $ sigAlg ;
275+
276+ return $ data ;
277+ }
195278}
0 commit comments