I want to create an Exchange Web Services (EWS) client application using Delphi XE6.
I am using a THttpRio component with a wsdl. How do I set the user credentials? In other languages, the equivalent of the THttpRio component has a Credentials property (example).
But this is missing from the Delphi component.
The authentication mechanism (apart from impersonation) is not part of the ews wsdl. It is native to the SOAP layer.
Listing 1:
procedure TForm1.Button1Click( Sender: TObject);
var
lESB : ExchangeServicePortType;
request : GetServiceConfiguration;
Impersonation : ExchangeImpersonation;
RequestVersion: RequestServerVersion;
MailboxCulture1: MailboxCulture;
GetServiceConfigurationResult: GetServiceConfigurationResponse;
ServerVersion : ServerVersionInfo;
begin
lESB := HTTPRIO1 as ExchangeServicePortType;
request := GetServiceConfiguration.Create;
request.RequestedConfiguration := ArrayOfServiceConfigurationType.Create( 'UnifiedMessagingConfiguration');
Impersonation := ExchangeImpersonation.Create;
RequestVersion := RequestServerVersion.Create;
MailboxCulture1 := MailboxCulture.Create;
GetServiceConfigurationResult:= GetServiceConfigurationResponse.Create;
ServerVersion := ServerVersionInfo.Create;
try
lESB.GetServiceConfiguration(
request, Impersonation, RequestVersion, MailboxCulture1,
GetServiceConfigurationResult, ServerVersion)
finally
request.Free;
Impersonation.Free;
RequestVersion.Free;
MailboxCulture1.Free;
GetServiceConfigurationResult.Free;
ServerVersion.Free
end
end;
Listing 1 above, shows some sample code, that I have tried so far. The purpose of the function is to get the version information about the server. HTTPRIO1 is a THTTPRIO component with default properties, and hooked up to the standard wsdl for EWS. This doesn't work because user credentials are not set.
How to set the user credentials?
After a lot of trial and error, this is the solution that I came up with...
(1) Authentication
If using the THTTPRio component, the UserName/Password pair of properties of the HTTPWebNode property of the THHPRio can be used to identifiy the credentials of the user, from the perspective of establishing an internet connection through a proxy server.
If using the THTTPReqResp component, the same UserName/Password properties are directly owned by the component.
If using the TIdHTTP, there are proxy related properties to use.
If you want to use the credentials of the currently logged on, with explicitly passing the password onto the component, you can achieve this by leaving blank, the UserName and Password properties of the THTTPRio and THTTPReqResp components. Proxy information will be automatically picked up from the system registry. However TIdHTTP is different. This component required proxy configuration to be set up explicitly, including the credentials of the internet user.
(2) What component to use for EWS Soap transactions?
I could not get the standard soap solution, the THTTPRio component, to work. The problem was that the THTTPRio component did not produce the correct shape of request envelope. Without going into detail, there were numerous issues such as elements that should have been placed in the body, would appear in the header instead, and schema violations. If you can get this (THTTPRio based on the published wsdl) to work, (but I doubt it), it is worth noting that the wsdl needs to be tweeked to include the service node. Microsoft deliberately excluded the service node for security reasons, but it is needed for correct operation of the Delphi soap wizard.
Developing a solution based on TIdHTTP is a non-starter, if your application sits behind a firewall and must negotiate with a proxy server.
The simplest and best solution is to use the THTTPReqResp component. You don't even need the wisdl for this. You must craft your own xml request envelopes, but with Microsoft's excellent documentation, showing simple envelopes, this task is a doddle.
(3) On using THTTPReqResp to access EWS
EWS uses UTF-8, so set UseUTF8InHeader := True
Set InvokeOptions = [soIgnoreInvalidCerts, soAutoCheckAccessPointViaUDDI]
Set the URL to the service server URL, as required.
Set SoapAction to http://schemas.microsoft.com/exchange/services/2006/messages
Set UserName and Password as required, or leave empty to use the credentials of the logged-on user.
EWS uses soap 1.1, not 1.2, so exclude wnoSOAP12 from WebNodeOptions.
Leverage MSDN online. It's a superb resource with plenty of examples.
(4) On building XML documents.
Soap envelopes are XML documents following a specific published format, with your request data embedded. How to build these envelopes?
Here two possible ways that worked well for me:
Use a template software design pattern. For example I use a component very similar to the TPageProducer. The source your basic template, and you use tag replacement to achieve your desired result. My version of the PageProducer can define optional fragments, or fragments that are repeated n times (with different data per iteration), marked up within the one template.
Use XSLT. This is the simplest solution. It is almost code free.
Here are two possible ways, that you might use, but I recommend against. It is not that they do not work, but just that the techniques are too clumsy and take too much coding:
Manually construct the document as a string (or use string builder) part-by-part.
Build the document, part-by-part using the standard XML interfaces (IXMLNode etc), and then stream out the result.
Related
As far as I can tell, this is the process to create an HTTPS request using Indy:
Create a TIdHTTP object
Use a TIdSSLIOHandlerSocketOpenSSL object as its IOHandler
Set up this TIdSSLIOHandlerSocketOpenSSL object's SSLOptions and SSLContext to get the proper behaviour before starting the request
However, Indy's documentation is quite minimal as for the possible values for these two SSLOptions and SSLContext objects, even to achieve what seems to me to be pretty standard behaviour.
In particular, I would like to know how to do the following:
Validate the certificate against either (depending on what is more straightforward) :
The local system's trust store
A list of root certificates provided with the application
Drop the connection if the certificate has not been correctly validated.
It seems to me to be the most basic behaviour for an application that needs to call base once in a while: you want to make sure you're really speaking to your own back-end, but still leave you the possibility of changing CAs if you ever need it.
I guess the SSLContext's field rootCertFile should be used, however:
Nowhere is it said in what format the rootCertFile should be provided (pem? der? pkcs something?)
It is in no way obvious how one should process to configure several alternatives root certificates.
Can someone provide the method, and if possible, some sample code on how this behaviour can be achieved?
I am not good with delphi yet, but based on some examples I have managed to create simple http server with no more than 10 users.
There are 2 main problems I don't know how to solve yet.
proper way to authenticate, manage users - sessions
main problem, connection must be secure, so SSL encryption is needed, how to implement it?
Any example I found in relation with idhttpserver and openssl, was not quite complete or with older version of Indy.
I am currently working with Delphi XE2 with Indy 10 components.
proper way to authenticate, manage users - sessions
TIdHTTPServer manages HTTP sessions for you if you set the TIdHTTPServer.SessionState property is true (it is false by default). TIdHTTPServer uses cookies for session management, so your clients need to have cookies enabled.
Authentication has to be performed manually, but how you do that depends on whether your clients are using HTTP-based or HTML-based authentication.
For HTTP authentication, there are ARequestInfo.UserName and ARequestInfo.Password properties available. If not valid, send an appropriate 401 response back to the client (if you set the AResponseInfo.AuthRealm property to a non-blank string, TIdHTTPServer will send a 401 response for you). By default, TIdHTTPServer only supports BASIC authentication. If you want to support other authentication schemes, you will have to use the TIdHTTPServer.OnParseAuthentication event, and send the 401 reply manually so you can send back appropriate WWW-Authenticate headers. Either way, if the client is validated, you can use HTTP sessions to keep the client logged in between requests. The AResponseInfo.Session and AResponseInfo.Session properties point at the current session. If TIdHTTPServer.AutoStartSession is true (it is false by default), TIdHTTPServer creates new sessions automatically. Otherwise, you can call TIdHTTPServer.CreateSession() yourself when needed. TIdHTTPSession has a Content property that you can store session-specific data in. Or you can derive a new class from TIdHTTPSession and then use the TIdHTTPServer.OnCreateSession event to create instances of that class.
For HTML authentication, you have two choices, depending on how you configure your HTML:
if your HTML <form> tag does not have an enctype attribute, or it is set to application/x-www-webform-urlencoded, TIdHTTPServer will store the raw webform data in the ARequestInfo.FormParams property, and if TIdHTTPServer.ParseParams is true (which it is by default), the data will also be parsed into the ARequestInfo.Params property for you.
if your HTML <form> tag has an enctype attribute set to multipart/form-data, you will have to parse the content of the ARequestInfo.PostStream manually, as TIdHTTPServer does not yet parse that data for you (examples have been posted many times before on many different forums on how to parse that data manually using Indy's TIdMessageDecoderMIME class). By default, ARequestInfo.PostStream points at a TMemoryStream object. You can use the TIdHTTPServer.OnCreatePostStream event to create an instance of a different TStream-derived class, if desired.
main problem, connection must be secure, so SSL encryption is needed, how to implement it?
Before activating the server:
assign a TIdServerIOHandlerSSLBase-derived component, such as TIdServerIOHandlerSSLOpenSSL, to the TIdHTTPServer.IOHandler property and configure it as needed (certificate, peer validation, SSL version(s), etc). In the case of OpenSSL, you will have to deploy the 2 OpenSSL library binaries libeay32.dll and ssleay32.dll (or non-Windows platform equivalents) with your app if they are not already pre-installed on the target OS, or if you want to ensure your app uses a specific version of OpenSSL. At this time, OpenSSL is the only encryption that Indy supports natively, but there are third-party solutions available that are compatible with Indy, such as EldoS SecureBlackbox.
fill in the TIdHTTPServer.Binding property with a binding for your desired HTTPS port (443 is the default HTTPS port). Typically you should create 2 bindings, one for HTTP port 80 and one for HTTPS port 443. Inside your OnCommand... handlers, if you receive a request that requires SSL/TLS encryption, you can check the port that the request was made on (AContext.Binding.Port) and if not HTTPS then redirect (AResponseInfo.Redirect()) the client to retry the request on the HTTPS port.
assign a handler to the TIdHTTPServer.OnQuerySSLPort event and have it set its VUseSSL parameter to True when its APort parameter matches your HTTPS port. UPDATE starting with SVN rev 5461, an OnQuerySSLPort handler is no longer needed if your only HTTPS port is 443.
Using Delphi XE to build a relatively straightforward database app using Datasnap.
Since some security in my application is handled at the database level, I need to pass a user's SQL credentials from my client app to my Datasnap server.
(I'm trying to make the Datasnap server stateless if possible, so recognise that I will have to do this for every call.)
I'm using ClientDatasets (CDS) on the client side so I could use OnBeforeGetRecords to pass the data in the OwnerData OleVariant from the CDS on the client to the corresponding TDataSetProvider on the server. But that means every single CDS on every data module has to have an event that does this, which seems messy and unwieldy. I can't help feeling there must be a way to pass messages to the server at a higher level than that.
What I'd really like is something like this at the DSServerClass level on the server side:
Procedure TMyServerContainer.MyServerClassCreateInstance(DSCreateInstanceEventObject: TDSCreateInstanceEventObject);
begin
// Server detects request for data from client app
fUsername := GetUsernameFromClientSomehow;
fPassword := GetPasswordFromClientSomehow;
// create data modules and initialise
MyDataModule := TMyDataModule.Create(nil);
MyDataModule.InitialiseWithSQLCredentials(fUsername, fPassword);
DSCreateInstanceEventObject.ServerClassInstance := MyDataModule;
End;
Could the Authentication Manager component help me here? Any other ideas? Or am I stuck with OnBeforeGetRecords?
Many thanks.
You can use the SQL credentials as UserName and Password for connecting to the DataSnap server. These values can be verified in the Authentication Manager and/or simply forwarded to the underlying SQLConnection component for connecting to the SQL server.
The most secure way would be to pass along the user security token (encrypted) and then use integrated security on the server side impersonating in a thread the calling user security context. This way no user/password would ever be sent across the wire. Unluckily while MS/DCE RPC can do this for every call (and DCOM, being built above RPC), Datasnap can't (SPNEGO/GSSAPI/SSPI looks to complex for the guys at Embarcadero, they like simple, unsecure protocols). Otherwise be very careful the way you send credential across the network, they could be easily sniffed unless properly protected.
I would advise you anyway to send them only once, if you need to (and in the most protected way you can), and then store them protected on the server side (suing Windows protected storage facilities), and send back to the client an handle/session token (tied to the originating IP), to be used in subsequent calls instead of resending credentials each time. Informations are cleared when the user logs off or the session timeouts.
We have a delphi XE application that uses SOAP (THTTPRIO etc) communications which (in delphi) works over WinInet.dll, by default. We fixed the authentication code so that it works, using https authentication, and when the user name and password for https are correct, everything is fine.
The problem is that when authentication details are incorrect, you get a message box from Windows, that is probably being popped up by WinInet.dll itself. I want to make that dialog box go away. I can't figure out how to change my Delphi SOAP so the password won't come up.
The situation is different than this question in the following ways:
I am doing all the things that he is doing, including calling InternetSetOption(...) to set the user name and password.
I am not using a server with a self-signed certificate, so the soIgnoreInvalidCerts flag is not applicable to my case.
Somehow, I think I need to get some API calls into WinInet to tell it not to pop up the InternetErrorDlg that it has (some versions of windows say Windows Security Options) that pops up to ask the user.
In my case the user name and password we have in our configuration file is being used, it is wrong (out of date) and so we want the WinInet code to simply return an error instead of popping up the dialog box.
Perhaps the other question the guy really did figure out how to do this, but the detail on that question is insufficient to see how he did it. The accepted answer does not work for me.
Some dead ends I've followed:
WinInet MSDN docs for PLUGIN_AUTH_FLAGS_CAN_HANDLE_UI - that doesn't appear to be applicable to a WinInet user, rather to a plugin.
WinInet MSDN docs discuss InternetSetOption, and some newsgroups have lead me to the following on-before-post event handler code:
procedure TMyDevice.HTTPWebNodeOnBeforePost(
const HTTPReqResp: SOAPHTTPTrans.THTTPReqResp; Data: Pointer);
var
SecurityFlagsLen:DWORD;
SecurityFlags:DWORD;
begin
{ authentication, NTLM+HTTPS, WinInet authentication set via WinInet SET INTERNET OPTION API.
This approach recommended on newsgroups for https basic authentication. }
if fUserName<>'' then
if not InternetSetOption(Data,
INTERNET_OPTION_USERNAME,
PChar(fUserName),
Length(fUserName)) then
raise EWebServiceAuthException.Create(SysErrorMessage(Windows.GetLastError));
if fPassword<>'' then
if not InternetSetOption(Data,
INTERNET_OPTION_PASSWORD,
PChar(fPassword),
Length (fPassword)) then
raise EWebServiceAuthException.Create(SysErrorMessage(Windows.GetLastError));
{ possible type of hackage: WinInet Security option flags to stop password box? }
SecurityFlagsLen := SizeOf(SecurityFlags);
InternetQueryOption({Request}data, INTERNET_OPTION_SECURITY_FLAGS,
Pointer(#SecurityFlags), SecurityFlagsLen);
SecurityFlags := SecurityFlags or SECURITY_FLAG_something;
InternetSetOption({Request}data, INTERNET_OPTION_SECURITY_FLAGS,
Pointer(#SecurityFlags), SecurityFlagsLen);
end;
This code makes the password work, but when the user's entered password is wrong, how do I get the SOAP call to fail, or raise an exception, instead of popping up a message box?
Replace WinINet by WinHTTP component. Both have very close APIs, and the 2nd does not create any UI interaction, but will return error codes, just like any other API. The UI part of WinINet may be a good idea for some software, but it sounds like if does not fit your needs.
See http://msdn.microsoft.com/en-us/library/windows/desktop/aa384068(v=vs.85).aspx
Of course, HTTPS and authentication will be handled in a similar manner. But you'll have to prompt for the user name and password, and update the HTTP headers as requested. See this link.
From our tests, WinHTTP is much faster than WinINet (certainly because it does not implement any UI part, and is not linked to Internet Explorer libraries).
You can take a look at our Open Source classes to guess how small is the difference in the API between WinINet and WinHTTP (most code is shared in the linked unit).
Try modifying SOAPHTTPTrans to handle the error silently.
In THTTPReqResp.HandleWinInetError, there is ultimately a call to the error dialog:
Result := CallInternetErrorDlg
You can probably detect your particular error, you should be able to return a 0 from HandleWinInetError, or at least NOT make a call to CallInternetErrorDlg. See if that helps.
I am writing a win32 service in Delphi (should be able to translate any other code if you don't know delphi) that needs to fetch the status of a website. I know how to do this in .NET but am not sure with normal windows based programming. So, I need to query a website and return the Status Code 200, 301 ect. My plan is then to parse the data returned and send an email should one of my sites go down.
Can someone lend a hand?
EDIT: This is the code I used in the end - using the TIDHttp Indy component.
IdHTTP.Get('http://www.example.com');
if IdHTTP.Connected then begin
ResponseCode := IntToStr(IdHTTP.ResponseCode);
ShowMessage(ResponseCode);
end;
Take Indy or Synapse library (both are free, indy is included with Delphi, synapse is found on google) and use their HTTP client class to do the job.