Rogier
Rogier

Reputation: 23

File permission to a specific application

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

Answers (1)

fpiette
fpiette

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

Related Questions