This article is based on a question seen on StackOverflow: File permission to a specific application.
(https://stackoverflow.com/questions/68457656/file-permission-to-a-specific-application).
In that question a developer ask how he can hide a data file so that the user cannot see it and yet have his application access the file.
A developer noted in a comment “If you don't want the configuration file to be readable you should encrypt the data and decrypt it as part of your program “. Another one said: “Windows security is user-based, not application-based. If the file is accessible to a given user, and that same user runs both your app and Notepad, then both apps will have access to the file “.
Sure, encryption is a valid solution to the problem. There are a lot of encryption/decryption libraries available for Delphi, including free one such as Delphi Encryption Compendium available for free on GitHub (https://github.com/MHumm/DelphiEncryptionCompendium).
I wanted to develop an alternate and probably easier method to access a data file from an application and yet prohibit the user access the file with another application and even prevent the user to see the file exists!
The key to this solution is to make the application logon as another user and store the file in that other user profile.
Sound complex? Maybe but actually, it is very simple. There are only two system calls required:
- LogonUser to provide credential for that user authentication (Username, domain and password) and get a handle.
- ImpersonateLoggedOnUser to ask Windows to momentarily forget about the current user and act as the newly logged on user.
- RevertToSelf
- CloseHandle.
type
TImpersonateUser = class(TComponent)
protected
FUserToken : THandle;
FErrorCode : DWORD;
public
destructor Destroy; override;
function Logon(const UserName : String;
const Domain : String;
const Password : String) : Boolean;
procedure Logoff();
property ErrorCode : DWORD read FErrorCode;
end;
The implementation is straightforward:
destructor TImpersonateUser.Destroy;
begin
if FUserToken <> 0 then begin
CloseHandle(FUserToken);
FUserToken := 0;
end;
inherited Destroy;
end;
procedure TImpersonateUser.Logoff;
begin
if FUserToken <> 0 then begin
RevertToSelf(); // Revert to our user
CloseHandle(FUserToken);
FUserToken := 0;
end;
end;
function TImpersonateUser.Logon(
const UserName : String;
const Domain : String;
const Password : String): Boolean;
var
LoggedOn : Boolean;
begin
Result := FALSE;
if FUserToken <> 0 then
Logoff();
if UserName = '' then begin // Must at least provide a user name
FErrorCode := ERROR_BAD_ARGUMENTS;
Exit;
end;
if Domain <> '' then
LoggedOn := LogonUser(PChar(UserName),
PChar(Domain),
PChar(Password),
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
FUserToken)
else
LoggedOn := LogonUser(PChar(UserName),
PChar(Domain),
PChar(Password),
LOGON32_LOGON_NEW_CREDENTIALS,
LOGON32_PROVIDER_WINNT50,
FUserToken);
if not LoggedOn then begin
FErrorCode := GetLastError();
Exit;
end;
if not ImpersonateLoggedOnUser(FUserToken) then begin
FErrorCode := GetLastError();
Exit;
end;
FErrorCode := ERROR_SUCCESS;
Result := TRUE;
end;
The sequences of operations in TImpersonateUser are limited to the thread issuing the calls. You may have – for example – the main thread acting as the current user and simultaneously have another thread acting as another user account of your choice. Of course, you need the credentials for that user account.
The use case for the developer who posted the question I mentioned in the beginning is to create a new Windows account for the sole purpose of storing the configuration files for all other users. The configuration files could be stored in a subdirectory of the Documents folder. One subdirectory for each other user using the application.
To further show how simple it is, I created a new TStream derived class that can directly access a file in another user account. I named this class TImpersonateFileStream. It derives from TFileStream, overriding the constructor to first impersonate the user (logon), then call the inherited TFileStream constructor and then immediately revert back (logoff) to the current Windows User. The stream remains opened with the access right of the given user event after logoff because the permissions are defined by the file handle when opened and persists during the live time of the file handle.
With TImpersonateFileStream you code exactly like with a TFileStream beside the 3 additional arguments of the constructor (Usercode, domain and password). If logon fails, you’ll get an exception.
The interface part is like this:
type
TImpersonateFileStream = class(TFileStream)
protected
FImpersonate : TImpersonateUser;
public
constructor Create(const AFileName : String;
const AMode : Word;
const AUserName : String;
const ADomain : String;
const APassword : String); overload;
destructor Destroy; override;
end;
and the implementation is very simple:
constructor TImpersonateFileStream.Create(
const AFileName : String;
const AMode : Word;
const AUserName : String;
const ADomain : String;
const APassword : String);
begin
// If no user name given, behave like a TFileStream (No cross account)
if AUserName = '' then begin
inherited Create(AFileName, AMode);
Exit;
end;
// A username is given, try to logon the user before opening the file
// and logoff once the file is opened (The file will be accessed as
// the user used to logon, even after logoff).
FImpersonate := TImpersonateUser.Create(nil);
if not FImpersonate.Logon(AUserName, ADomain, APassword) then
raise EImpersonateFileStream.CreateFmt('Logon error %d. %s.',
[FImpersonate.ErrorCode,
SysErrorMessage(FImpersonate.ErrorCode)]);
try
inherited Create(AFileName, AMode);
finally
FImpersonate.Logoff;
end;
end;
destructor TImpersonateFileStream.Destroy;
begin
FreeAndNil(FImpersonate);
inherited Destroy;
end;
I made a demo with edit box to specify the user account credentials and the file, and a few buttons to call various TStream methods.
It is easy to write code similar to TImpersonateFileStream.Create, to do other operation like DeleteFile or RenameFile. Just put that code between logon and logoff as is TImpersonateFileStream.Create.
Full source code for the article, including demo, can be found on GitHub at https://github.com/fpiette/Delphi-ImpersonateUser
If you need help with this code, please use StackOverflow.com to ask for your question, be sure to use the tag #delphi so that I receive a notification. I’ll try to answer your question. Before asking, please review “How to Ask” https://stackoverflow.com/questions/how-to-ask