@@ -47,9 +47,6 @@ Root: HKLM; Subkey: "Software\Red Hat\{#AppName}"; Flags: uninsdeletekey
4747Root : HKLM; Subkey : " Software\Red Hat\{#AppName}" ; ValueType : string ; ValueName : " InstallDir" ; ValueData : " {app} "
4848
4949[Code]
50- // function xxxx
51- // external 'xxx@files:EfTidy.dll stdcall setuponly';
52-
5350
5451type
5552 ComponentGroup = record
6461 Install: Boolean;
6562 end ;
6663
64+ SAMLFormParams = record
65+ Action: String;
66+ SAMLRequest: String;
67+ SAMLResponse: String;
68+ RelayState: String;
69+ end ;
70+
6771var
6872 // Page IDs
6973 AuthPageID, ComponentPageID, DownloadPageID, GetStartedPageID, InstallPageID : Integer;
@@ -104,13 +108,107 @@ begin
104108 end ;
105109end ;
106110
107- procedure LoginButtonOnClick (Sender: TObject);
111+ // Converts a value to hex
112+ function DigitToHex (Digit: Integer): Char;
113+ begin
114+ if (Digit >= 0 ) and (Digit <= 9 ) then
115+ Result := Chr(Digit + Ord(' 0' ))
116+ else if (Digit >= 10 ) and (Digit <= 15 ) then
117+ Result := Chr(Digit - 10 + Ord(' A' ))
118+ else
119+ Result := ' 0' ;
120+ end ;
121+
122+ // URL-encodes the provided string
123+ function URLEncode (const S: string): string;
124+ var
125+ i, idx, len: Integer;
126+ begin
127+ len := 0 ;
128+ for i := 1 to Length(S) do
129+ if ((S[i] >= ' 0' ) and (S[i] <= ' 9' )) or
130+ ((S[i] >= ' A' ) and (S[i] <= ' Z' )) or
131+ ((S[i] >= ' a' ) and (S[i] <= ' z' )) or (S[i] = ' ' ) or
132+ (S[i] = ' _' ) or (S[i] = ' *' ) or (S[i] = ' -' ) or (S[i] = ' .' ) then
133+ len := len + 1
134+ else
135+ len := len + 3 ;
136+
137+ SetLength(Result, len);
138+ idx := 1 ;
139+
140+ for i := 1 to Length(S) do
141+ if S[i] = ' ' then
142+ begin
143+ Result[idx] := ' +' ;
144+ idx := idx + 1 ;
145+ end
146+ else if ((S[i] >= ' 0' ) and (S[i] <= ' 9' )) or
147+ ((S[i] >= ' A' ) and (S[i] <= ' Z' )) or
148+ ((S[i] >= ' a' ) and (S[i] <= ' z' )) or
149+ (S[i] = ' _' ) or (S[i] = ' *' ) or (S[i] = ' -' ) or (S[i] = ' .' ) then
150+ begin
151+ Result[idx] := S[i];
152+ idx := idx + 1 ;
153+ end
154+ else
155+ begin
156+ Result[idx] := ' %' ;
157+ Result[idx + 1 ] := DigitToHex(Ord(S[i]) div 16 );
158+ Result[idx + 2 ] := DigitToHex(Ord(S[i]) mod 16 );
159+ idx := idx + 3 ;
160+ end ;
161+ end ;
162+
163+ function ExtractSAMLFormValues (ResponseText: String): SAMLFormParams;
108164var
109- Url, Resource, ResponseText, SAMLRequest, RelayState: String;
110165 I, J, K: Integer;
166+ XMLDoc, NodeList, FormNode, InputNode: Variant;
167+ begin
168+ XMLDoc := CreateOleObject(' MSXML2.DOMDocument' );
169+ XMLDoc.async := False;
170+ XMLDoc.resolveExternals := False;
171+ XMLDoc.validateOnParse := False;
172+ XMLDoc.setProperty(' ProhibitDTD' , False);
173+ XMLDoc.loadXML(ResponseText);
174+
175+ if XMLDoc.parseError.errorCode <> 0 then
176+ begin
177+ MsgBox(' Error on line ' + IntToStr(XMLDoc.parseError.line) + ' , position ' +
178+ IntToStr(XMLDoc.parseError.linepos) + ' : ' + XMLDoc.parseError.reason, mbInformation, MB_OK);
179+ end else begin
180+ NodeList := XMLDoc.getElementsByTagName(' form' );
181+
182+ for I := 0 to NodeList.length - 1 do
183+ begin
184+ FormNode := NodeList.item(i);
185+
186+ Result.Action := FormNode.attributes.getNamedItem(' action' ).nodeValue;
187+
188+ for J := 0 to FormNode.childNodes.length - 1 do
189+ begin
190+ if FormNode.childNodes.item(J).nodeName = ' input' then
191+ begin
192+ InputNode := FormNode.childNodes.item[J];
193+
194+ if InputNode.attributes.getNamedItem(' name' ).nodeValue = ' SAMLRequest' then
195+ Result.SAMLRequest := InputNode.attributes.getNamedItem(' value' ).nodeValue;
196+ if InputNode.attributes.getNamedItem(' name' ).nodeValue = ' SAMLResponse' then
197+ Result.SAMLResponse := InputNode.attributes.getNamedItem(' value' ).nodeValue;
198+ if InputNode.attributes.getNamedItem(' name' ).nodeValue = ' RelayState' then
199+ Result.RelayState := InputNode.attributes.getNamedItem(' value' ).nodeValue;
200+ end ;
201+ end ;
202+ end ;
203+ end ;
204+ end ;
205+
206+ procedure LoginButtonOnClick (Sender: TObject);
207+ var
208+ Url, Resource, ResponseText, RequestText: String;
111209 Page: TWizardPage;
112- Button: TNewButton ;
113- WinHttpReq, EfTidy, XMLDoc, NodeList, FormNode, InputNode: Variant ;
210+ WinHttpReq, EfTidy: Variant ;
211+ SAMLValues: SAMLFormParams ;
114212begin
115213 Page := PageFromID(AuthPageID);
116214
@@ -127,9 +225,13 @@ begin
127225
128226 // Perform a SAML authentication for redhat.com
129227 WinHttpReq := CreateOleObject(' WinHttp.WinHttpRequest.5.1' );
130- WinHttpReq.Open(' POST ' , Url, false);
228+ WinHttpReq.Open(' GET ' , Url, false);
131229 WinHttpReq.SetClientCertificate(' LOCAL_MACHINE\Personal\My Certificate' );
132230 WinHttpReq.SetRequestHeader(' Content-Type' , ' application/x-www-form-urlencoded' );
231+
232+ // Temporary - set proxy so Fiddler can inspect the traffic
233+ // WinHttpReq.SetProxy( 2, '127.0.0.1:8888');
234+
133235 WinHttpReq.Send();
134236
135237 if WinHttpReq.Status <> 200 then
@@ -144,64 +246,82 @@ begin
144246 AuthLabel.Caption := ' Authentication Successful.' ;
145247 AuthLabel.Font.Color := clGreen;
146248
147- // MsgBox(WinHttpReq.ResponseText, mbInformation, MB_OK);
148-
149-
150- // Perform a SAML authentication for redhat.com
151- WinHttpReq := CreateOleObject(' WinHttp.WinHttpRequest.5.1' );
152- WinHttpReq.Open(' GET' , Resource, false);
153- WinHttpReq.SetClientCertificate(' LOCAL_MACHINE\Personal\My Certificate' );
154- WinHttpReq.SetRequestHeader(' Content-Type' , ' application/x-www-form-urlencoded' );
155- WinHttpReq.Send();
156-
157- ResponseText := WinHttpReq.ResponseText;
158-
249+ // Register the EfTidy dll so that we can call its functions
159250 ExtractTemporaryFile(' EfTidy.dll' );
160251 RegisterServer(True, ExpandConstant(' {tmp}\EfTidy.dll' ), False);
161252
162- // Tidy up the (non well-formed) response with EfTidy
253+ // We are going to tidy up the (non well-formed) response with EfTidy -
254+ // the first step is to create the EfTidy object
163255 EfTidy := CreateOleObject(' EfTidy.tidyCom' );
164256 EfTidy.Option.Clean := True;
165257 EfTidy.Option.OutputType := 1 ; // XhtmlOut
166258 EfTidy.Option.DoctypeMode := 3 ; // DoctypeLoose
167- ResponseText := EfTidy.TidyMemToMem(ResponseText);
168259
169- XMLDoc := CreateOleObject(' MSXML2.DOMDocument' );
170- XMLDoc.async := False;
171- XMLDoc.resolveExternals := False;
172- XMLDoc.validateOnParse := False;
173- XMLDoc.setProperty(' ProhibitDTD' , False);
174- XMLDoc.loadXML(ResponseText);
260+ // Tidy up the response to make it valid XML so we can parse it
261+ ResponseText := EfTidy.TidyMemToMem(WinHttpReq.ResponseText);
262+
263+ // MsgBox('Got response: ' + ResponseText, mbInformation, MB_OK);
175264
176- if XMLDoc.parseError.errorCode <> 0 then
265+ // Parse the now-valid XML response and extract the values we're interested in
266+ SAMLValues := ExtractSAMLFormValues(ResponseText);
267+
268+ RequestText := ' SAMLRequest=' + URLEncode(SAMLValues.SAMLRequest) +
269+ ' &RelayState=' + URLEncode(SAMLValues.RelayState);
270+
271+ // POST the SAMLRequest and RelayState to the URL specified in the returned action attribute,
272+ // which in this case should be the IdP
273+ WinHttpReq.Open(' POST' , SAMLValues.Action, false);
274+ WinHttpReq.SetRequestHeader(' Content-Type' , ' application/x-www-form-urlencoded' );
275+ WinHttpReq.SetRequestHeader(' Content-Length' , IntToStr(Length(RequestText)));
276+ WinHttpReq.Send(RequestText);
277+
278+ // MsgBox('Sending request: ' + RequestText, mbInformation, MB_OK);
279+
280+ if WinHttpReq.Status <> 200 then
177281 begin
178- MsgBox(' Error on line ' + IntToStr(XMLDoc.parseError.line) + ' , position ' +
179- IntToStr(XMLDoc.parseError.linepos) + ' : ' + XMLDoc.parseError.reason, mbInformation, MB_OK);
282+ AuthLabel.Caption := ' Authentication Failed.' ;
283+ AuthLabel.Font.Color := clRed;
284+ Exit;
180285 end else begin
181- NodeList := XMLDoc.getElementsByTagName(' form' );
286+ // Tidy up the response to make it valid XML so we can parse it
287+ ResponseText := EfTidy.TidyMemToMem(WinHttpReq.ResponseText);
182288
183- for I := 0 to NodeList.length - 1 do
184- begin
185- FormNode := NodeList.item(i);
289+ // MsgBox('Got response: ' + ResponseText, mbInformation, MB_OK);
186290
187- for J := 0 to FormNode.childNodes.length - 1 do
188- begin
189- if FormNode.childNodes.item(J).nodeName = ' input ' then
190- begin
191- InputNode := FormNode.childNodes.item[J];
192-
193- if InputNode.attributes.getNamedItem( ' name ' ).nodeValue = ' SAMLRequest ' then
194- SAMLRequest := InputNode.attributes.getNamedItem( ' value ' ).nodeValue;
195- if InputNode.attributes.getNamedItem( ' name ' ).nodeValue = ' RelayState ' then
196- RelayState := InputNode.attributes.getNamedItem( ' value ' ).nodeValue;
197- end ;
198- end ;
199- end ;
200- end ;
291+ // Extract the Action, SAMLResponse and RelayState parameter values from the response
292+ SAMLValues := ExtractSAMLFormValues(ResponseText);
293+
294+ { MsgBox('Posting SAMLResponse to ' + SAMLValues.Action + ', Request length: ' +
295+ IntToStr(Length(SAMLValues.SAMLResponse)) +
296+ ', SAMLResponse: ' + Copy(SAMLValues.SAMLResponse, 1, 1000) +
297+ ', RelayState: ' + SAMLValues.RelayState,
298+ mbInformation, MB_OK); }
299+
300+ RequestText := ' SAMLResponse= ' + URLEncode(SAMLValues.SAMLResponse) +
301+ ' &RelayState= ' + URLEncode(SAMLValues.RelayState);
302+
303+ // MsgBox('Sending request: ' + Copy(RequestText, 0, 1000), mbInformation, MB_OK) ;
304+ WinHttpReq.Open( ' POST ' , SAMLValues.Action, false) ;
201305
306+ // Do not follow redirects here
307+ WinHttpReq.Option(6 ) := False;
202308
203- // MsgBox('SAMLRequest: ' + SAMLRequest + ' RelayState: ' + RelayState, mbInformation, MB_OK);
309+ WinHttpReq.SetRequestHeader(' Content-Type' , ' application/x-www-form-urlencoded' );
310+ WinHttpReq.SetRequestHeader(' Content-Length' , IntToStr(Length(RequestText)));
311+ WinHttpReq.Send(RequestText);
204312
313+ if WinHttpReq.Status <> 302 then
314+ begin
315+ AuthLabel.Caption := ' Authentication Failed.' ;
316+ AuthLabel.Font.Color := clRed;
317+ Exit;
318+ end else begin
319+ MsgBox(' Got cookies: ' + WinHttpReq.getResponseHeader(' Set-Cookie' ), mbInformation, MB_OK);
320+
321+ // MsgBox('Got response code: ' + WinHttpReq.Status + ', response: ' +
322+ // WinHttpReq.ResponseText, mbInformation, MB_OK);
323+ end ;
324+ end ;
205325
206326 // Simulate a click of the Next button
207327 WizardForm.NextButton.OnClick(nil );
0 commit comments