@@ -16,6 +16,19 @@ class SamlTokenProvider extends BaseTokenProvider
16
16
*/
17
17
private static $ StsUrl = 'https://login.microsoftonline.com/extSTS.srf ' ;
18
18
19
+ /**
20
+ * RST2 URL
21
+ * @var string
22
+ */
23
+ private static $ RST2Url = 'https://login.microsoftonline.com/rst2.srf ' ;
24
+
25
+ /**
26
+ * To Get the STS authentication url if $StsUrl request fails.
27
+ * @var string
28
+ */
29
+ private static $ RealmUrlTemplate = 'https://login.microsoftonline.com/getuserrealm.srf?login={username}&xml=1 ' ;
30
+
31
+
19
32
/**
20
33
* Form Url to submit SAML token
21
34
* @var string
@@ -24,6 +37,20 @@ class SamlTokenProvider extends BaseTokenProvider
24
37
25
38
26
39
/**
40
+ * Boolean to determine whether the system is using Federated STS or not.
41
+ * @var
42
+ */
43
+ protected $ usingFederatedSTS ;
44
+
45
+ /**
46
+ * Form Url to submit SAML token if Federated STS is set.
47
+ * @var string
48
+ */
49
+ private static $ IDCRLSVCPageUrl = '/_vti_bin/idcrl.svc/ ' ;
50
+
51
+
52
+
53
+ /**
27
54
* @var string
28
55
*/
29
56
protected $ authorityUrl ;
@@ -41,15 +68,26 @@ class SamlTokenProvider extends BaseTokenProvider
41
68
*/
42
69
private $ rtFa ;
43
70
71
+ /**
72
+ * Federated STS Auth Cookie
73
+ * @var
74
+ */
75
+ private $ SPOIDCRL ;
76
+
44
77
45
78
public function __construct ($ authorityUrl )
46
79
{
47
80
$ this ->authorityUrl = $ authorityUrl ;
81
+ $ this ->usingFederatedSTS = FALSE ;
48
82
}
49
83
50
84
51
85
public function getAuthenticationCookie ()
52
86
{
87
+ if ($ this ->usingFederatedSTS ) {
88
+ return 'SPOIDCRL= ' . $ this ->SPOIDCRL ;
89
+ }
90
+
53
91
return 'FedAuth= ' . $ this ->FedAuth . '; rtFa= ' . $ this ->rtFa ;
54
92
}
55
93
@@ -73,11 +111,27 @@ public function acquireToken($parameters)
73
111
protected function acquireAuthenticationCookies ($ token )
74
112
{
75
113
$ urlInfo = parse_url ($ this ->authorityUrl );
114
+
76
115
$ url = $ urlInfo ['scheme ' ] . ':// ' . $ urlInfo ['host ' ] . self ::$ SignInPageUrl ;
77
- $ response = Requests::post ($ url ,null ,$ token ,true );
78
- $ cookies = Requests::parseCookies ($ response );
79
- $ this ->FedAuth = $ cookies ['FedAuth ' ];
80
- $ this ->rtFa = $ cookies ['rtFa ' ];
116
+ if ($ this ->usingFederatedSTS ) {
117
+ $ url = $ urlInfo ['scheme ' ] . ':// ' . $ urlInfo ['host ' ] . self ::$ IDCRLSVCPageUrl ;
118
+
119
+ $ headers = array ();
120
+ $ headers ['User-Agent ' ] = '' ;
121
+ $ headers ['X-IDCRL_ACCEPTED ' ] = 't ' ;
122
+ $ headers ['Authorization ' ] = 'BPOSIDCRL ' . $ token ;
123
+ $ headers ['Content-Type ' ] = 'application/x-www-form-urlencoded ' ;
124
+
125
+ $ response = Requests::getHead ($ url ,$ headers ,$ token ,true );
126
+ $ cookies = Requests::parseCookies ($ response );
127
+ $ this ->SPOIDCRL = $ cookies ['SPOIDCRL ' ];
128
+ }
129
+ else {
130
+ $ response = Requests::post ($ url ,null ,$ token ,true );
131
+ $ cookies = Requests::parseCookies ($ response );
132
+ $ this ->FedAuth = $ cookies ['FedAuth ' ];
133
+ $ this ->rtFa = $ cookies ['rtFa ' ];
134
+ }
81
135
}
82
136
83
137
@@ -93,31 +147,119 @@ protected function acquireSecurityToken($username, $password)
93
147
{
94
148
$ data = $ this ->prepareSecurityTokenRequest ($ username , $ password , $ this ->authorityUrl );
95
149
$ response = Requests::post (self ::$ StsUrl ,null ,$ data );
150
+
151
+ try {
152
+ $ this ->processSecurityTokenResponse ($ response );
153
+ }
154
+ catch (Exception $ e ) {
155
+ // Try to get the token with a federated authentication.
156
+ $ response = $ this ->acquireSecurityTokenFromFederatedSTS ($ username , $ password );
157
+
158
+ }
96
159
return $ this ->processSecurityTokenResponse ($ response );
97
160
}
98
161
162
+ /**
163
+ * Acquire the service token from Federated STS
164
+ *
165
+ * @param string $username
166
+ * @param string $password
167
+ * @return string
168
+ */
169
+ protected function acquireSecurityTokenFromFederatedSTS ($ username , $ password ) {
170
+
171
+ $ response = Requests::get (str_replace ('{username} ' , $ username , self ::$ RealmUrlTemplate ),null );
172
+ $ federatedStsUrl = $ this ->getFederatedAuthenticationInformation ($ response );
173
+
174
+ if ($ federatedStsUrl ) {
175
+ $ message_id = md5 (uniqid ($ username . '- ' . time () . '- ' . rand () , true ));
176
+ $ data = $ this ->prepareSecurityFederatedTokenRequest ($ username , $ password , $ message_id , $ federatedStsUrl ->textContent );
177
+
178
+ $ headers = array ();
179
+ $ headers ['Content-Type ' ] = 'application/soap+xml ' ;
180
+ $ response = Requests::post ($ federatedStsUrl ->textContent , $ headers , $ data );
181
+
182
+ $ samlAssertion = $ this ->getSamlAssertion ($ response );
183
+
184
+ if ($ samlAssertion ) {
185
+ $ samlAssertion_node = $ samlAssertion ->item (0 );
186
+ $ data = $ this ->prepareRST2Request ($ samlAssertion_node );
187
+ $ response = Requests::post (self ::$ RST2Url , $ headers , $ data );
188
+ $ this ->usingFederatedSTS = TRUE ;
189
+
190
+ return $ response ;
191
+ }
192
+ }
193
+
194
+ return NULL ;
195
+ }
196
+
197
+ /**
198
+ * Get SAML assertion Node so it can be used within the RST2 template
199
+ * @param $response
200
+ * @return \DOMNodeList|null
201
+ */
202
+ protected function getSamlAssertion ($ response ) {
203
+ $ xml = new \DOMDocument ();
204
+ $ xml ->loadXML ($ response );
205
+ $ xpath = new \DOMXPath ($ xml );
206
+
207
+ if ($ xpath ->query ("//*[name()='saml:Assertion'] " )->length > 0 ) {
208
+ $ nodeToken = $ xpath ->query ("//*[name()='saml:Assertion'] " );
209
+ if (!empty ($ nodeToken )) {
210
+ return $ nodeToken ;
211
+ }
212
+ }
213
+ return NULL ;
214
+ }
215
+
216
+ /**
217
+ * Retrieves the STS federated URL if any.
218
+ * @param $response
219
+ * @return string Federated STS Url
220
+ */
221
+ protected function getFederatedAuthenticationInformation ($ response ) {
222
+ if ($ response ) {
223
+ $ xml = new \DOMDocument ();
224
+ $ xml ->loadXML ($ response );
225
+ $ xpath = new \DOMXPath ($ xml );
226
+ if ($ xpath ->query ("//STSAuthURL " )->length > 0 ) {
227
+ return $ xpath ->query ("//STSAuthURL " )->item (0 );
228
+ }
229
+ }
230
+ return '' ;
231
+ }
99
232
100
233
/**
101
234
* Verify and extract security token from the HTTP response
102
235
* @param mixed $response
103
- * @return mixed
236
+ * @return mixed BinarySecurityToken or Exception when an error is present
104
237
* @throws Exception
105
238
*/
106
239
protected function processSecurityTokenResponse ($ response )
107
240
{
108
241
$ xml = new \DOMDocument ();
109
242
$ xml ->loadXML ($ response );
110
243
$ xpath = new \DOMXPath ($ xml );
244
+ if ($ xpath ->query ("//wsse:BinarySecurityToken " )->length > 0 ) {
245
+ $ nodeToken = $ xpath ->query ("//wsse:BinarySecurityToken " )->item (0 );
246
+ if (!empty ($ nodeToken )) {
247
+ return $ nodeToken ->nodeValue ;
248
+ }
249
+ }
250
+
111
251
if ($ xpath ->query ("//S:Fault " )->length > 0 ) {
112
252
$ nodeErr = $ xpath ->query ("//S:Fault/S:Detail/psf:error/psf:internalerror/psf:text " )->item (0 );
113
253
throw new \Exception ($ nodeErr ->nodeValue );
114
254
}
115
- $ nodeToken = $ xpath ->query ("//wsse:BinarySecurityToken " )->item (0 );
116
- if (empty ($ nodeToken )) {
117
- throw new \RuntimeException ('Error trying to get a token, check your URL or credentials ' );
255
+
256
+ if ($ xpath ->query ("//S:Fault " )->length > 0 ) {
257
+ $ nodeErr = $ xpath ->query ("//S:Fault/S:Detail/psf:error/psf:internalerror/psf:text " )->item (0 );
258
+ throw new \Exception ($ nodeErr ->nodeValue );
118
259
}
119
260
120
- return $ nodeToken ->nodeValue ;
261
+ throw new \RuntimeException ('Error trying to get a token, check your URL or credentials ' );
262
+
121
263
}
122
264
123
265
/**
@@ -142,4 +284,59 @@ protected function prepareSecurityTokenRequest($username, $password, $address)
142
284
$ template = str_replace ('{address} ' , $ address , $ template );
143
285
return $ template ;
144
286
}
287
+
288
+ /**
289
+ * Construct the request body to acquire security token from Federated STS endpoint (sts.yourcompany.com)
290
+ *
291
+ * @param $username
292
+ * @param $password
293
+ * @param $message_uuid
294
+ * @param $federated_sts_url
295
+ * @return string
296
+ * @throws Exception
297
+ */
298
+ protected function prepareSecurityFederatedTokenRequest ($ username , $ password , $ message_uuid , $ federated_sts_url )
299
+ {
300
+ $ fileName = __DIR__ . '/xml/federatedSAML.xml ' ;
301
+ if (!file_exists ($ fileName )) {
302
+ throw new \Exception ("The file $ fileName does not exist " );
303
+ }
304
+
305
+ $ template = file_get_contents ($ fileName );
306
+ $ template = str_replace ('{username} ' , $ username , $ template );
307
+ $ template = str_replace ('{password} ' , $ password , $ template );
308
+ $ template = str_replace ('{federated_sts_url} ' , $ federated_sts_url , $ template );
309
+ $ template = str_replace ('{message_uuid} ' , $ message_uuid , $ template );
310
+ return $ template ;
311
+ }
312
+
313
+ /**
314
+ * Prepare the request to be sent to RST2 endpoint with the saml assertion
315
+ * @param $samlAssertion
316
+ * @return bool|mixed|string
317
+ * @throws \Exception
318
+ */
319
+ protected function prepareRST2Request ($ samlAssertion )
320
+ {
321
+
322
+ $ fileName = __DIR__ . '/xml/RST2.xml ' ;
323
+ if (!file_exists ($ fileName )) {
324
+ throw new \Exception ("The file $ fileName does not exist " );
325
+ }
326
+ $ template = file_get_contents ($ fileName );
327
+
328
+ $ xml = new \DOMDocument ();
329
+ $ xml ->loadXML ($ template );
330
+ $ xpath = new \DOMXPath ($ xml );
331
+
332
+ $ samlAssertion = $ xml ->importNode ($ samlAssertion , true );
333
+ if ($ xpath ->query ("//*[name()='wsse:Security'] " )->length > 0 ) {
334
+ $ parentNode = $ xpath ->query ("//wsse:Security " )->item (0 );
335
+ //append "saml assertion" node to <wsse:Security> node
336
+ $ parentNode ->appendChild ($ samlAssertion );
337
+ return $ xml ->saveXML ();
338
+ }
339
+
340
+ return NULL ;
341
+ }
145
342
}
0 commit comments