AD authentication using Unicode - delphi

Just implemented AD Authentication in C# using:
DirectoryEntry entry =
new DirectoryEntry(_path, domainAndUsername, pwd, AuthenticationTypes.Secure);
where _path is LDAP://+ full qualified domain name (eg. the ip of the domain controler).
Now I have to do the same using Delphi. So I found Solomon's excelent Delphi 2007 LDAP implementation at http://www.freemeg.com/index.php/projects/projects-2/15-delphi-ldap-authentication-component
Have anyone a working version for Delphi 2009+ (unicode)?
Have anyone a working sample with simple AD Authentication processing(eg. validating) domain\userid and password?
In C# the nice part is that I don't need to traverse the AD - I simply performs a one level search via LDAP - just to check if the user is authenticated.

Tony Caduto have provided me with a Synapse solution:
I cut this stuff out of a authentication object I created, I don't want to post the whole thing since there is a bunch of other non related stuff in it.
This should get you going, the key is to concatenate the AD username with '#your.ad.domain.name'
After you succesfully bind, you can then do searches against the AD directory by supplying a base DN
and using the search function of the ldapsend unit.
I have found this to be faster than other methods and it's solid. You do need to get the trunk version of
synapse so it works with the later versions of delphi.
uses ldapsend
var
fldap:tldapsend;
fad_domain,ausername,apassword:string;
begin
ausername:='your AD username';
apassword:='your AD password';
fldap := TLDAPSend.Create;
fad_domain:= 'your.ad.domain';
fldap.TargetHost:=fad_domain;
//next line is the key to getting AD authentication working
fldap.UserName := ausername+'#'+fad_domain;
fldap.Password := apassword;
try
try
if fldap.Login then
if fldap.Bind then
begin
//user is succesfully authenticated at this point
end else
raise exception.Create('LDAP bind failed.');
except
on e:exception do
//whatever
end;
finally
fldap.logout;
freeandnil(fldap);
end;
end;
Thanks to Tony!!!!

Related

Synapse LDAPSend library requires username = Domain\User

I'm using Synapse LDAPSend.pas library to connect and authenticate in an Active Directory server. The code is pretty simple and direct, but the problem is that the username needs "DomainName\Username" or "Username#DomainName" format to bind command works correctly. Are there anyhow to make it work only with Username specified?
var
ldap: TLDAPsend;
begin
ldap:= TLDAPsend.Create;
try
ldap.TargetHost := '192.168.0.12';
ldap.UserName:= 'AD\Owner'; //here I need to specify only Owner
ldap.Password:= 'Password!';
if ldap.Login then
if ldap.Bind then
begin
//do stuff
ldap.Logout;
end;
finally
ldap.Free;
end;
end;
I was thinking maybe something like "*\Owner", or any other command from Active Directory to make it works? Or maybe a Synapse setting to make it.
If the current user is logged on to the domain there is a variety of options to retrieve the domain name. The easiest way is to read the environment variable USERDOMAIN using the function SysUtils.GetEnvironmentVariable. There are also the Windows API functions NetWkstaUserGetInfo available on Windows NT and better as well as GetUserNameEx introduced with Windows 2000 among a lot of others, which are usually harder to use.
If your version of Delphi does not contain declarations for these API functions please use the Jedi Windows API headers.

Push to Specific User

Writing in Delphi 10 Seattle, targeting the 'pusher' as Windows, and the receiver as iOs (for now). The aim is to be able to push a message to a specific user without relying on broadcast and client side filtering.
I've managed to achieve the following so far:
1. Send a broadcast push to my iOS app
2. Login as a user on my iOS app
3. Create a pointer in my Installation record for User -> _User
That's as far as I can get in Delphi. From what I understand, I have to update the Installation record on login to reflect the installations logged in user.
I cannot find out how to do that in Delphi's Parse/BAAS objects. What seems to be missing is that I can't get the logged in users Installation ID. I assume if I could I could then update that via the TBackendStorage Class.
Any help would be appreciated.
I've cross posted this question to Embarcadero forums and community site.
To retrieve the User Object Id
var
ACreatedObject: TBackendEntityValue; // REST.Backend.MetaTypes
begin
BackendUsers.Users.LoginUser('donald','#duck99',ACreatedObject);
fUserObjectId:=ACreatedObject.ObjectID;
end
To Update the Installation with the User Object iD
Assuming you have a column 'User' in your Installation table on Parse as a pointer to _User class.
Within the PushEventsDeviceRegistered Event:
var
JO,JOP:TJSonObject; // System.JSON
O:TBackendEntityValue; // REST.Backend.MetaTypes
begin
if PushEvents.InstallationValue.TryGetObjectID(fInstallationObjectId) then
begin
try
JO:=TJSONObject.Create;
JOP:=TJSONObject.Create;
JOP.AddPair('__type','Pointer');
JOP.AddPair('className','_User');
JOP.AddPair('objectId',fUserObjectId);
JO.AddPair('User',JOP);
BackendStorage.Storage.UpdateObject('_Installation',
fInstallationObjectId,JO,O);
finally
JO.Free;
end;
end
end;
To Target the Push Message Based on User Name
Note, you could create the target via JSON objects,but I've used a string with formatting here.
const
Target = '{"where":{"User":{"$select":{"query":'+
'{"__type":"Pointer","className":"_User","where":'+
'{"username":"%s"}},"key":"objectId"}}}}';
begin
BackendPush.Target.Text:=Format(Target,['donald']);
BackendPush.Message:='Gratz on the Election Result';
BackendPush.Push;
end
Non Local Variables/Declarations
The following Delphi BaaS components were created at Design time.
ParseProvider: TParseProvider;
PushEvents: TPushEvents;
BackendUsers: TBackendUsers;
BackendStorage: TBackendStorage;
BackendPush: TBackendPush;
The following class (or global) variables are referred to:
fUserObjectId:string; // Must be set before push registration is activated.
fInstallationObjectId:string;
NOTE: The original code is fully tested, however I've cut/paste (and edited to remove non-relevant stuff) so forgive me if there are any cut/paste errors.

Imap4 client command LSUB

I have a problem with function TIdIMAP4.ListSubscribedMailBoxes(AMailBoxList: TStrings): Boolean; with this implementation :
function TIdIMAP4.ListSubscribedMailBoxes(AMailBoxList: TStrings): Boolean;
begin
{CC2: This is one of the few cases where the server can return only "OK completed"
meaning that the user has no subscribed mailboxes.}
Result := False;
CheckConnectionState([csAuthenticated, csSelected]);
SendCmd(NewCmdCounter, IMAP4Commands[cmdLSub] + ' "" *',
[IMAP4Commands[cmdList], IMAP4Commands[cmdLSub]]); {Do not Localize}
if LastCmdResult.Code = IMAP_OK then begin
// ds - fixed bug # 506026
ParseLSubResult(AMailBoxList, LastCmdResult.Text);
Result := True;
end;
end;
When I debug I see that the LastCmdResult.Text stringlist is empty, but the LastCmdResult.FormattedReply stringlist has all folders on my email server (Inbox, Sent, Trash, ...). When I tried to use LastCmdResult.FormattedReply count or text, it had immediately lost its data and gave LastCmdResult.FormattedReply.Count=0 and LastCmdResult.FormattedReply.Text=''. So I'd like to know if there is a way to enter the data inside LastCmdResult.FormattedReply and get my email server folders or there is another way to solve my problem ?
I have a problem with function TIdIMAP4.ListSubscribedMailBoxes(AMailBoxList: TStrings): Boolean; with this implementation :
Works fine for me when I try it using the latest SVN version of Indy.
When I debug I see that the LastCmdResult.Text stringlist is empty, but the LastCmdResult.FormattedReply stringlist has all folders on my email server (Inbox, Sent, Trash, ...).
When I run it, the opposite happens. LastCmdResult.Text contains the expected text, and LastCmdResult.FFormattedReply is empty (notice I mention the FFormattedReply data member directly, see below).
When I tried to use LastCmdResult.FormattedReply count or text, it had immediately lost its data and gave LastCmdResult.FormattedReply.Count=0 and LastCmdResult.FormattedReply.Text=''.
That is by design. The FormattedReply property is intended to be used by a client to parse a server reply so it can populate TIdReply's property values, and to be used by a server to generate a new reply using TIdReply's property values. So, you cannot read from the FormattedReply property on the client side.
So I'd like to know if there is a way to enter the data inside LastCmdResult.FormattedReply and get my email server folders or there is another way to solve my problem ?
The whole purpose of ListSubscribedMailBoxes() is to return the folder names in the AMailBoxList parameter. If that is not working for you, then either
you are using a older/buggy version of Indy.
your server is sending the data in a format that TIdIMAP4 is not able to parse.
Without knowing which version of Indy you are actually using, or what the server's reply data actually looks like, there is no way to diagnose your issue one way or the other.

Advantage Table File in use error. How can I resolve?

I'm having trouble getting a certain table to open up in more then one instance of my program.
Whats happening is I'm trying to allow users to open up and replace a current table(part of a data dictionary - FileForm.ImagesTable) with an older table (not included in the data dictionary). It works great for one instance of the program but when we try to open up that same file simultaneously on another instance. I get the following error.
FileName.ADT This file is in use. Enter a new name or close the file that's open in another program.
Below is the code I have reassigning the table name and datapath to the selected table.
OpenDialog1.FileName := '*.adt';
OpenDialog1.Filter := 'Software 6.0 Files (*.adt)|*.adt|Software 5.x Files (*.dbf)|*.dbf';
OpenDialog1.InitialDir := DataPath;
if OpenDialog1.Execute then
begin
Str1 := Trim(OpenDialog1.FileName);
if Length(Str1) = 0 then
Exit;
DSImage.Enabled := False;
with FileForm.ImagesTable do
begin
Active := False;
AfterOpen := FileForm.TableOther.AfterOpen;
DataBaseName := ExtractFilePath(Str1);
TableName := ExtractFileName(Str1);
Active := True;
end;
end;
Edit * Using Advtantage 8.1, Seems to be a windows error because the error happens in the dialogue window. And yes Exclusive is set to false.
Any thougths on why this is happening and how this could be resolved are appreciated.
Thanks
You're not clear on the specific error - is it a Windows error or an Advantage error?
If it's a Windows error, it may be because you've specified exclusive access to the table (ImageTable.Exclusive = True). This would mean that the first instance of the app could open it, but subsequent tries would fail with a File is in use error.
If it's an Advantage error, the Advantage help file (here in v11's documentation, since you didn't specify a version of ADS - note it's in a frame, so you may need to use this link, navigate to Advantage Developers Guide, expand the Part 1->Chapter 4 - Dictionaries->Understanding Dictionaries topic) says:
A data dictionary is a special file that serves as the sole access point for database tables
Note the sole access point. Once a table is in the data dictionary, it belongs to the data dictionary. You're trying to replace that reference with something outside the scope of the dictionary, and that isn't allowed. I'm pretty sure that the problem is related to that - ADS puts a proprietary lock on tables that are included in the dictionary, and controls access to those files through the server by way of the dictionary.
You'll need to either remove the table from the dictionary and use it as a free table, or come up with a different strategy for removing the current data and replacing it with other data to preserve the integrity of the dictionary.
It looks like you are only using the Open Dialog to get the name of the table.
On the Open dialog try setting the option ofShareAware
OpenDialog1.Options := OpenDialog1.Options + [ ofShareAware ];
Once the table is open with Advantage the mode is both deny write, deny read and as a result will return a sharing error if anything non-advantage tries to open the table.

Overbyte ICS HTTPS POST

I'm wanting to create a CloudFlare client in the Firemonkey framework. For those who don't know, CloudFlare serves as a CDN of sorts for anyone with a website. They have an API available, and as with many web API's, they are using JSON with a token-based system. It requires both the account email address and the account token to access the API. It runs on HTTPS, and as you can imagine, attempting to access the API via HTTP/non-SSL simply produces null results.
The application i wish to create would serve as an all-in-one management tool, intending to eliminate the need for me to use a web browser to manage my CloudFlare settings. I'm having the most basic of issues; SSL POST. See, i can submit an API request via a web browser and get a list of results (e.g. https://www.cloudflare.com/api_json.html?a=stats&z=DOMAIN&u=EMAIL&tkn=TOKEN - Personal details removed for obvious reasons), but i'm unsure how i would go about getting these same results (or any results from the API for that matter) in Firemonkey.
I've got Overbyte ICS with SSL installed, as well as the basic bundled Indy components, but i'm struggling to get started with this. I need to post a list of parameters to https://www.cloudflare.com/api_json.html via HTTPS/SSL, but i've very little idea on where to start. I've seen a few various example around SO, mostly using ICS, but i've been unable to find any specific to posting with multiple parameters, how i should format it, etc.
One example i tried was using ICS TSSLHttpCli, writing my parameters as a single string (i.e. a=stats&z=DOMAIN&u=EMAIL&tkn=TOKEN), writing that to the SendStream of TSSLHttpCli, seeking to 0,0, setting the URL (i.e. https://www.cloudflare.com/api_json.html?), and then calling the Post method. However, this gives me Connection aborted on request. This is the code i've tried (though i've replaced personal details with generic values);
var
Data : AnsiString;
RcvStrm, SndStrm : TMemoryStream;
begin
SndStrm := TMemoryStream.Create;
RcvStrm := TMemoryStream.Create;
Data := '?a=stats&z=MYDOMAIN&u=MYEMAIL&tkn=MYTOKEN';
SslHttpCli.SendStream := SndStrm;
SslHttpCli.SendStream.Write(Data[1],Length(Data));
SslHttpCli.SendStream.Seek(0,0);
Memo1.Lines.LoadFromStream(SndStrm);
ShowMessage('Waiting!');
SslHttpCli.RcvdStream := RcvStrm;
SslHttpCli.URL := 'https://www.cloudflare.com/api_json.html';
SslHttpCli.Post;
Memo1.Lines.Clear;
Memo1.Lines.LoadFromStream(RcvStrm);
Memo1.Lines.Add('.....');
RcvStrm.Free;
SndStrm.Free;
ShowMessage('Complete!');
end;
The ShowMessage procedures are simply there to provide a visual break so i can see what data is in the stream at each time. When Memo1.Lines.LoadFromStream(SndStrm); is called, i get a single question mark the contents of the Data in the memo as expected.
When i call Memo1.Lines.LoadFromStream(RcvStrm);, i expect it to add the return result from the API, and then the 5 dots underneath it. However, this does not happen, and it's apparent that the message i'm receiving is related to the issue. I'm assuming i've not set up the data correctly, but i'm simply unsure exactly how i should format it prior to attempting to post it. I've even commented out everything below Memo1.Lines.LoadFromStream(RcvStrm); to the end to see whether the Clear procedure is called on the memo, but the contents of the memo remain the same as they were when i called LoadFromStream(SndStrm). The final ShowMessage is also not called.
I initially tried using String instead of AnsiString, but this simply output the first character of Data rather than the whole string.
There could be numerous reasons why it's not working (all details for API access are correct, so it's an issue with the code), but i need someone with more experience and knowledge to point me in the right direction.
My network coding knowledge is limited, and i've only dealt with basic SQL and FTP in Delphi so far. I've still got to work with the parsed JSON once i do get past this step, but for now, can anyone assist me in this endeavor so i can get started?
I noticed you seemed to solve this with a GET request, but I noticed two immediate problems with your POST request:
as Runner Suggested, drop the '?' in your data. The '?' is only used when appending parameters to the URL in a GET request.
You never set the content type of the HTTP Request (should be application/x-www-form-urlencoded). You can do this with the following code:
SSLHttpCli.ContentTypePost := 'application/x-www-form-urlencoded';
Just a helpful thought. I checked https://www.cloudflare.com/docs/client-api.html and they mention that POST requests are accepted. It's possible the server rejects requests that have any other content type.
Just some food for thought if you ever need to contact another API via POST requests and want to use the Overbyte Components.
Hope the info is useful!
Try this;
SndStrm := TMemoryStream.Create;
RcvStrm := TMemoryStream.Create;
Data := 'a=stats&z=MYDOMAIN&u=MYEMAIL&tkn=MYTOKEN';
SndStrm.Write(Data[1], Length(Data));
SndStrm.Seek(0, 0);
SslHttpCli.SendStream := SndStrm;

Resources