Search code examples
windowsdelphifile-permissionswindows-servergroup-policy

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.


Solution

  • 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