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