Reputation: 23
I made application in Delphi and it is running on Windows Server 2019 per user basis. Those users connect with Remote Desktop to the user session (group policy) and run the application.
Is it possible to open a configuration file on a shared network map only with my application and not for example with Notepad?
More in general. What is the best way to store configuration data which in fact is secret for the user? I was thinking to put sensitive data just in the database but still it is nice no put for example server information somehwere in a config file instead of "baking" in.
This is my first post an I am aware it is between programming and server configuration. Else my translation scales seem no to get a hit for "only application open a file". My excuses if this post isnt perfect.
Upvotes: 2
Views: 846
Reputation: 12312
I see several possibilities:
1° If you don't want the user to "see" your data, then you have to encrypt the file content. There are a lot of Delphi encryption/decryption libraries. I suggest you start with Delphi Encryption Compendium which is available for free on GitHub.
You can store the data in an in-memory structure such as an XML or JSON (Delphi has built-in routine to handle both XML and JSON). Before writing to disc, you encrypt it and after having reloaded the encrypted file, you decrypt it before accessing it the standard way.
2° Use a file accessible from another account and make your program impersonate that account when access to the file is required.
I wrote some code for use to ease and demo that way. I created a class TImpersonateUser
having two methods Logon
and Logoff
which will make the program connect to a given user account and disconnect from it.
To test, first logon using another account and create a file somewhere, for example in the documents. Then logon back to your normal user code and launch the demo program (code below). Fill username, domain and password (For domain, "." will authenticate only on local computer). Fill the filename with complete path of the file you created previously. The click "file access". It should answer "file not found". Then click "Impersonate" and again "File Access". Now you should have access to the file in the other account. Click on "Revert to self" and try again "File Access" it should fail again.
In summary, for your question, the data the user cannot see must be created under another account and you application impersonate that other account when it needs to access the data. Don't forget to somehow hide username and password in your program.
Note: Once you get a handle (file or stream opened), you can RevertToSelf and still use the handle (or stream). It keeps the security context (the account used) until closed. This means you can call Logon before opening the file, call logoff right after opening (or failure of opening) and continue to access the file.
EDIT: I wrote a blog post with more code.
unit ImpersonateUser;
interface
uses
Winapi.Windows, System.Classes;
const
LOGON32_LOGON_NEW_CREDENTIALS = 9; // Missing in Delphi
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;
implementation
{ TImpersonateUser }
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 := S_OK;
Result := TRUE;
end;
end.
Simple demo:
unit ImpersonateUserDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
ImpersonateUser;
type
TImpersonateUserMainForm = class(TForm)
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
UserNameEdit: TEdit;
DomainEdit: TEdit;
PasswordEdit: TEdit;
ImpersonateButton: TButton;
Label4: TLabel;
FileNameEdit: TEdit;
RevertToSelfButton: TButton;
FileAccessButton: TButton;
procedure FileAccessButtonClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ImpersonateButtonClick(Sender: TObject);
procedure RevertToSelfButtonClick(Sender: TObject);
private
FImpersonate : TImpersonateUser;
end;
var
ImpersonateUserMainForm: TImpersonateUserMainForm;
implementation
{$R *.dfm}
procedure TImpersonateUserMainForm.FileAccessButtonClick(Sender: TObject);
var
Stream : TFileStream;
begin
try
if not FileExists(FileNameEdit.Text) then
ShowMessage('File not found')
else begin
Stream := TFileStream.Create(FileNameEdit.Text, fmOpenRead);
try
ShowMessage('File opened');
finally
Stream.Free;
end;
end;
except
on E:Exception do
ShowMessage(E.Classname + ': ' + E.Message);
end;
end;
procedure TImpersonateUserMainForm.FormCreate(Sender: TObject);
begin
UserNameEdit.Text := 'YourUsername';
DomainEdit.Text := '.';
PasswordEdit.Text := 'YourPassword';
FilenameEdit.Text := 'C:\Users\AnotherUser\Documents\HelloWorld.txt';
FImpersonate := TImpersonateUser.Create(Self);
end;
procedure TImpersonateUserMainForm.ImpersonateButtonClick(Sender: TObject);
begin
if not FImpersonate.Logon(UserNameEdit.Text,
DomainEdit.Text,
PasswordEdit.Text) then begin
ShowMessage(Format('Failed with error 0x%X', [FImpersonate.ErrorCode]));
end
else
ShowMessage('Logon OK');
end;
procedure TImpersonateUserMainForm.RevertToSelfButtonClick(Sender: TObject);
begin
FImpersonate.Logoff;
ShowMessage('Reverted to self');
end;
end.
The DFM file:
object ImpersonateUserMainForm: TImpersonateUserMainForm
Left = 0
Top = 0
Caption = 'ImpersonateUserMainForm'
ClientHeight = 142
ClientWidth = 331
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 16
Top = 20
Width = 49
Height = 13
Caption = 'UserName'
end
object Label2: TLabel
Left = 16
Top = 48
Width = 35
Height = 13
Caption = 'Domain'
end
object Label3: TLabel
Left = 12
Top = 76
Width = 46
Height = 13
Caption = 'Password'
end
object Label4: TLabel
Left = 16
Top = 104
Width = 16
Height = 13
Caption = 'File'
end
object UserNameEdit: TEdit
Left = 80
Top = 16
Width = 121
Height = 21
TabOrder = 0
Text = 'UserNameEdit'
end
object DomainEdit: TEdit
Left = 80
Top = 44
Width = 121
Height = 21
TabOrder = 1
Text = 'DomainEdit'
end
object PasswordEdit: TEdit
Left = 80
Top = 72
Width = 121
Height = 21
TabOrder = 2
Text = 'PasswordEdit'
end
object ImpersonateButton: TButton
Left = 232
Top = 14
Width = 75
Height = 25
Caption = 'Impersonate'
TabOrder = 3
OnClick = ImpersonateButtonClick
end
object FileNameEdit: TEdit
Left = 80
Top = 99
Width = 121
Height = 21
TabOrder = 4
Text = 'FileNameEdit'
end
object RevertToSelfButton: TButton
Left = 232
Top = 45
Width = 75
Height = 25
Caption = 'Revert to self'
TabOrder = 5
OnClick = RevertToSelfButtonClick
end
object FileAccessButton: TButton
Left = 232
Top = 76
Width = 75
Height = 25
Caption = 'File access'
TabOrder = 6
OnClick = FileAccessButtonClick
end
end
Upvotes: 3