MySQL/MariaDB: Implement support for expired passwords. Show a change-password dialog after the very first query of a connection when it returns "Error 1820: You must SET PASSWORD before executing this statement". See http://www.heidisql.com/forum.php?t=17921

This commit is contained in:
Ansgar Becker
2016-06-13 17:59:05 +00:00
parent 350d9ba487
commit 75fecfd3dd
5 changed files with 330 additions and 3 deletions

View File

@@ -41,7 +41,8 @@ uses
syncdb in '..\..\source\syncdb.pas' {frmSyncDB},
gnugettext in '..\..\source\gnugettext.pas',
JumpList in '..\..\source\JumpList.pas',
extra_controls in '..\..\source\extra_controls.pas';
extra_controls in '..\..\source\extra_controls.pas',
change_password in '..\..\source\change_password.pas' {frmPasswordChange};
{$R ..\..\res\icon.RES}
{$R ..\..\res\icon-question.RES}

View File

@@ -194,6 +194,10 @@
<DCCReference Include="..\..\source\gnugettext.pas"/>
<DCCReference Include="..\..\source\JumpList.pas"/>
<DCCReference Include="..\..\source\extra_controls.pas"/>
<DCCReference Include="..\..\source\change_password.pas">
<Form>frmPasswordChange</Form>
<FormType>dfm</FormType>
</DCCReference>
<BuildConfiguration Include="Debug">
<Key>Cfg_2</Key>
<CfgParent>Base</CfgParent>

162
source/change_password.dfm Normal file
View File

@@ -0,0 +1,162 @@
object frmPasswordChange: TfrmPasswordChange
Left = 0
Top = 0
BorderStyle = bsDialog
Caption = 'Change expired password'
ClientHeight = 187
ClientWidth = 456
Color = clBtnFace
Constraints.MaxHeight = 300
Constraints.MaxWidth = 600
Constraints.MinHeight = 185
Constraints.MinWidth = 400
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
Position = poMainFormCenter
OnShow = FormShow
DesignSize = (
456
187)
PixelsPerInch = 96
TextHeight = 13
object lblHeading: TLabel
Left = 8
Top = 16
Width = 440
Height = 51
Anchors = [akLeft, akTop, akRight, akBottom]
AutoSize = False
Caption = 'lblHeading'
WordWrap = True
end
object lblPassword: TLabel
Left = 8
Top = 76
Width = 74
Height = 13
Anchors = [akLeft, akBottom]
Caption = 'New password:'
end
object lblRepeatPassword: TLabel
Left = 8
Top = 103
Width = 111
Height = 13
Anchors = [akLeft, akBottom]
Caption = 'Repeat new password:'
end
object lblStatus: TLabel
Left = 8
Top = 132
Width = 41
Height = 13
Anchors = [akLeft, akBottom]
Caption = 'lblStatus'
end
object editPassword: TButtonedEdit
Left = 146
Top = 73
Width = 302
Height = 21
Anchors = [akLeft, akRight, akBottom]
Images = MainForm.ImageListMain
RightButton.DropDownMenu = popupPassword
RightButton.ImageIndex = 75
RightButton.Visible = True
TabOrder = 0
TextHint = 'Type new password or select a suggestion'
OnChange = editPasswordChange
OnClick = editPasswordChange
OnKeyDown = editPasswordKeyDown
end
object editRepeatPassword: TButtonedEdit
Left = 146
Top = 100
Width = 302
Height = 21
Anchors = [akLeft, akRight, akBottom]
Images = MainForm.ImageListMain
PasswordChar = '*'
TabOrder = 1
OnChange = editPasswordChange
OnClick = editPasswordChange
OnKeyDown = editPasswordKeyDown
end
object btnCancel: TButton
Left = 292
Top = 154
Width = 75
Height = 25
Anchors = [akRight, akBottom]
Caption = 'Cancel'
ModalResult = 2
TabOrder = 2
end
object btnOK: TButton
Left = 213
Top = 154
Width = 75
Height = 25
Anchors = [akRight, akBottom]
Caption = 'OK'
Default = True
ModalResult = 1
TabOrder = 3
end
object btnCopyToClipboard: TButton
Left = 373
Top = 154
Width = 75
Height = 25
Anchors = [akRight, akBottom]
Caption = 'Copy'
ImageIndex = 3
Images = MainForm.ImageListMain
TabOrder = 4
OnClick = btnCopyToClipboardClick
end
object popupPassword: TPopupMenu
Left = 344
Top = 8
object N6characters1: TMenuItem
Caption = '6 characters'
OnClick = menuPasswordClick
object menuDummy1: TMenuItem
Caption = 'dummy'
OnClick = menuPasswordInsert
end
end
object N8characters1: TMenuItem
Caption = '8 characters'
OnClick = menuPasswordClick
object menuDummy2: TMenuItem
Caption = 'dummy'
end
end
object N10characters1: TMenuItem
Caption = '10 characters'
OnClick = menuPasswordClick
object menuDummy3: TMenuItem
Caption = 'dummy'
end
end
object N12characters1: TMenuItem
Caption = '12 characters'
OnClick = menuPasswordClick
object menuDummy4: TMenuItem
Caption = 'dummy'
end
end
object N30characters1: TMenuItem
Caption = '30 characters'
OnClick = menuPasswordClick
object menuDummy5: TMenuItem
Caption = 'dummy'
end
end
end
end

137
source/change_password.pas Normal file
View File

@@ -0,0 +1,137 @@
unit change_password;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, extra_controls, gnugettext,
Vcl.Menus, Clipbrd;
type
TfrmPasswordChange = class(TFormWithSizeGrip)
lblHeading: TLabel;
lblPassword: TLabel;
lblRepeatPassword: TLabel;
editPassword: TButtonedEdit;
editRepeatPassword: TButtonedEdit;
btnCancel: TButton;
btnOK: TButton;
lblStatus: TLabel;
popupPassword: TPopupMenu;
N6characters1: TMenuItem;
N8characters1: TMenuItem;
N10characters1: TMenuItem;
N12characters1: TMenuItem;
N30characters1: TMenuItem;
menuDummy1: TMenuItem;
menuDummy2: TMenuItem;
menuDummy3: TMenuItem;
menuDummy4: TMenuItem;
menuDummy5: TMenuItem;
btnCopyToClipboard: TButton;
procedure editPasswordChange(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure editPasswordKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure menuPasswordClick(Sender: TObject);
procedure menuPasswordInsert(Sender: TObject);
procedure btnCopyToClipboardClick(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
frmPasswordChange: TfrmPasswordChange;
implementation
uses main, helpers;
{$R *.dfm}
procedure TfrmPasswordChange.FormShow(Sender: TObject);
begin
// Manually trigger change event on password box
editPassword.OnChange(Sender);
end;
procedure TfrmPasswordChange.menuPasswordClick(Sender: TObject);
var
Parent, Item: TMenuItem;
PasswordLen, i: Integer;
begin
// Create menu items with random passwords
Parent := Sender as TMenuItem;
PasswordLen := MakeInt(Parent.Caption);
for i:=0 to 19 do begin
if Parent.Count > i then
Item := Parent[i]
else begin
Item := TMenuItem.Create(Parent);
Parent.Add(Item);
end;
Item.OnClick := menuPasswordInsert;
Item.Caption := GeneratePassword(PasswordLen);
end;
end;
procedure TfrmPasswordChange.menuPasswordInsert(Sender: TObject);
var
Item: TMenuItem;
begin
// Insert password from menu item
Item := Sender as TMenuItem;
editPassword.Text := Item.Caption;
editRepeatPassword.Text := editPassword.Text;
end;
procedure TfrmPasswordChange.btnCopyToClipboardClick(Sender: TObject);
var
OldImageIndex: Integer;
begin
// Copy new password to clipboard
Clipboard.AsText := editPassword.Text;
OldImageIndex := btnCopyToClipboard.ImageIndex;
btnCopyToClipboard.ImageIndex := 55;
btnCopyToClipboard.Repaint;
Sleep(500);
btnCopyToClipboard.ImageIndex := OldImageIndex;
end;
procedure TfrmPasswordChange.editPasswordChange(Sender: TObject);
begin
// User has entered something on one or both password fields
btnOK.Enabled := False;
btnCopyToClipboard.Enabled := False;
if editPassword.Text = '' then begin
editPassword.PasswordChar := #0;
lblStatus.Caption := _('Please change your password')
end else begin
editPassword.PasswordChar := '*';
if editPassword.Text <> editRepeatPassword.Text then
lblStatus.Caption := _('Error: Passwords do not match!')
else begin
lblStatus.Caption := '';
btnOK.Enabled := True;
btnCopyToClipboard.Enabled := True;
end;
end;
end;
procedure TfrmPasswordChange.editPasswordKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
editPassword.OnChange(Sender);
end;
end.

View File

@@ -757,7 +757,7 @@ var
implementation
uses helpers, loginform;
uses helpers, loginform, change_password;
{ TProcessPipe }
@@ -1614,6 +1614,7 @@ var
sslca, sslkey, sslcert, sslcipher: PAnsiChar;
PluginDir: AnsiString;
Vars, Status: TDBQuery;
PasswordChangeDialog: TfrmPasswordChange;
begin
if Value and (FHandle = nil) then begin
DoBeforeConnect;
@@ -1667,7 +1668,7 @@ begin
end;
// Gather client options
ClientFlags := CLIENT_LOCAL_FILES or CLIENT_INTERACTIVE or CLIENT_PROTOCOL_41 or CLIENT_MULTI_STATEMENTS;
ClientFlags := CLIENT_LOCAL_FILES or CLIENT_INTERACTIVE or CLIENT_PROTOCOL_41 or CLIENT_MULTI_STATEMENTS or CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS;
if Parameters.Compressed then
ClientFlags := ClientFlags or CLIENT_COMPRESS;
if Parameters.WantSSL then
@@ -1708,6 +1709,28 @@ begin
'* either to fix the "%s" variable,'+CRLF+
'* or to grant you missing privileges.'),
['init_connect', 'init_connect']);
// Try to fire the very first query against the server, which probably run into the following error:
// "Error 1820: You must SET PASSWORD before executing this statement"
try
ThreadId;
except
on E:EDatabaseError do begin
if GetLastErrorCode = 1820 then begin
PasswordChangeDialog := TfrmPasswordChange.Create(Self);
PasswordChangeDialog.lblHeading.Caption := GetLastError;
PasswordChangeDialog.ShowModal;
if PasswordChangeDialog.ModalResult = mrOk then begin
if ExecRegExpr('\sALTER USER\s', GetLastError) then
Query('ALTER USER USER() IDENTIFIED BY '+EscapeString(PasswordChangeDialog.editPassword.Text))
else
Query('SET PASSWORD=PASSWORD('+EscapeString(PasswordChangeDialog.editPassword.Text)+')');
end else // Dialog cancelled
Raise;
PasswordChangeDialog.Free;
end else
Raise;
end;
end;
Log(lcInfo, f_('Connected. Thread-ID: %d', [ThreadId]));
try
CharacterSet := 'utf8mb4';