Пример ядра для клиента БД - Post Scriptum(Авторизация и Итог)

Опубликовано Dozent в ЧТ, 21/08/2014 - 03:58



Меня просили рассказать об авторизации и правах доступа в проекте.
Ну для начала нам понадобится md5hash.pas (Будет в архиве)
При записке программы выполняется событие onShow главной формы и в нём пишем

procedure TfrmMainForm.FormShow(Sender: TObject);
begin
//вызываем форму авторизации если резёльтат закрытия не положительынй тогда разрываем соединение с базой и закрываем главную форму
if frmLogin.ShowModal <> mrOk then
begin
Connection.Connected := false;
frmMainForm.Close;
end;

end;

Сама форма авторизации обычная едит для ввода пароля и кнопка ок и отмена (для кнопки отмена свойство ModalResult устанавливаем в mrCancel)
Мб кто не знает, весь смысл авторизации это узнать имеется ли введенный логин в базе и сравнить захэшированую строку из едита с захэшированым значением в базе у найденного логина.
Я не буду делать лишний комментариев, те комменты которые я делал при разработке и так дают понять что происходит при нажатии кнопки "Войти"

Спойлер
procedure TfrmLogin.btnOkClick(Sender: TObject);
var
login, pswd: string;
begin
// получения данных о пользователях
try
qLogin.Close;
qLogin.SQL.Clear;
qLogin.SQL.Text := LoginSQL;
qLogin.Open;

// Проверка логина
login := eLogin.Text;
if not qLogin.Locate('login', login, []) then
begin
MessageBox(Handle, PChar('Пользователь с таким логином не найден!' +
#13#10'Введите существующий логин...'), PChar('Ошибка'), MB_ICONERROR);
eLogin.Clear;
Exit; // Выходим из проверки авторизации
end;

// Проверка пароля
pswd := md5(ePswd.Text);
if qLogin.FieldByName('user_pswd').AsString <> pswd then
begin
MessageBox(Handle, PChar('Введёный пароль не верный!' +
#13#10'Повторите ввод пароля...'), PChar('Ошибка'), MB_ICONERROR);
Exit; // Выходим из проверки авторизации
end
else // проверяем не отключён ли пользователь
begin
if qLogin.FieldByName('active').AsInteger = 0 then
begin
MessageBox(Handle, PChar('Данный пользователь отключён!' +
#13#10'Обратительс к администратору...'), PChar('Ошибка'),
MB_ICONERROR);
Exit; // Выходим из проверки авторизации
end
else
begin // УСПЕШНАЯ АВТОРИЗАЦИЯ!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
frmLogin.Close;
ModalResult := mrOk;
PermissionValue := qLogin.FieldByName('per_value').AsInteger;
// если не админ
if PermissionValue <> 3 then
begin
// Скрываем сервис
frmMainForm.MainMenu.Items.Items[2].Items[0].Enabled := False;
// скрывает резервное копирование/востановление
frmMainForm.MainMenu.Items.Items[0].Items[0].Enabled := False;
frmMainForm.MainMenu.Items.Items[0].Items[1].Enabled := False;
end;
if PermissionValue < 1 then
frmMainForm.MainMenu.Items.Items[2].Enabled := False;
frmMainForm.StatusBar1.Panels[0].Width := 120;
frmMainForm.StatusBar1.Panels[0].Text := 'Статус: Подключен';
frmMainForm.StatusBar1.Panels[1].Width := 500;
frmMainForm.StatusBar1.Panels[1].Text :=
Format('Вы авторизировались как: %s',
[qLogin.FieldByName('prsn_fio').AsString]);
end;
end;

finally
ePswd.Clear;
qLogin.Close;
end;
end;

Ну это не единственное ограничение в правах доступа, которые я произвёл в проекте, есть там замудрёный функционал с отрядами и караулами.
Я выделил отдельный уровень доступа, что то вроде "Начальник караула".
У нас есть свойство на форме авторизации в которое записывается уровень доступа( вы можете сделать глобальную переменную)
property PermissionValue: integer read FPermissionValue
write FPermissionValue;
И к этому свойству (глобальной переменой) я могу обращаться из любого места проекта
Поэтому я гдето там сделал так если при открытии караула уровень доступа авторизировщегося пользователя не нач. караула то сообщение

if frmLogin.PermissionValue < 2 then begin MessageBox(Handle, PChar('Не достаточно прав на изменение' + #13#10'Изменять отряды, может только Начальник караула'), PChar('Ошибка'), MB_ICONERROR); exit; end; Вот и всё))) Структуру БД можно посмотреть, я в какойто из частей прикреплял скрин.

Ну а теперь хочется подвести итог.
Что лично я могу выделить в этом?
После создания базовых форм и отладки кода в них, мне не нужно было для каждой задачи, рисовать интерфейс, подгонять его, я нажимаю пару кнопок и у меня готовая к работе форма!!!
Далее мне не нужно из формы в форму переписывать одинаковый код изменяя в нём названия полей и их количество, за меня всё это делает "ядро".
Если у меня что-то не так работает в ядре или мне нужно добавить кнопку на все формы, я правлю баг в базовой или добавляю кнопку в базовой и все изменения появляются на всех формах созданых на основе базовых.

Я переложил на плечи базовых форм:
- Интерфейс форм (панельки, кнопки, грид)
- Получение данных
- Открытие карточек
- Сохранение данных

Это тот минимум, который многие переписывают из формы в форму...

На этом заканчиваю если есть вопросы пишите!!!

До свидания

!

ВложениеРазмер
Forms.rar8.54 КБ

Комментарии

11 комментария(ев)
аватар: DrBlack
DrBlack
Дата: ЧТ, 21/08/2014 - 04:56
Звание: Мастер
Сообщений: 1787

Dozent, спасибо тебе, статьи классные, было очень интересно почитать! Good
Зы - надеюсь качество курсовых и дипломных поднимется, мало где так хорошо описывают примеры разработки... (
Будь у меня такой мануал, в свое время, на дипломной я бы всех раком поставил! Laughing out loud

аватар: Dozent
Dozent
Дата: ЧТ, 21/08/2014 - 06:49
Звание: Энтузиаст
Сообщений: 115

Спасибо:)

аватар: rdama
rdama
Дата: ВС, 24/08/2014 - 02:18
Звание: Наблюдатель
Сообщений: 44

Ну как понимаю мы пересылаем на клиента md5 правильного пароля. Ну и насколько я помню md5 не самый стойкий вариант шифрования.
Да еще потом накручиваем проверки на тему не забаннености пользователя.
Вобщем на мой взгляд слишком много кода для проверок.
Я бы предложил использовать хранимку на стороне БД. И передавать туда некое существо содержащее логин и пароль в куче, а можно еще и волшебное слово и дату и смещение туда влепить, главное на стороне БД это потом разобрать.
Далеена стороне сервера проверяем это самое безобразие и клиенту выдаем результат в виде 1/0 ну и соответственно можно/нельзя.
Кода может будет не меньше в итоге, но проверка на клиенте и пересылка правильногопароля исчезнет.
А вообще в целом конечно цикл статей занимательный получился. Разве, что много кода.
Его может быть меньше. Вовсяком случае у меня обычно меньше получается. Правда я неучитываю свои дописки в VCL и всю ту часть
которую стараюсь сделать повозможности универсальной и просто в виде подключаемых юнитов.

аватар: Dozent
Dozent
Дата: ЧТ, 28/08/2014 - 05:52
Звание: Энтузиаст
Сообщений: 115

Вы меня уже простите:) Но ваш коммент написан так как будто вы просто что то хотели написать:)
Кода не так уж и много, как раз совсем наоборот.
По мне так, никогда в базе не должна хранится логика, вся логика должна быть в коде!
Ну и про надёжность md5, мб мб:)

аватар: Spider_NET
Spider_NET
Дата: ВС, 31/08/2014 - 19:10
Звание: Мастер
Сообщений: 2454

Зря вы так, хранимые процедуры применять нужно и в больших приложениях такой подход себя вполне оправдывает. Чем больше вы логике перенесете на базу, тем меньше придется обновлять клиент по каждому чиху. Если у вас несколько десятков инсталляций, то подход оправдает себя при незначительном обновлении. Вот пример из жизни. Потребовалось изменить алгоритм авторизации на проекте с 200 пользователями в разных часовых поясах. Поскольку логика проверки была реализована в виде хранимой процедуры, то клиенты просто ничего не заметили Smile Клиентская часть просто обратилась к хранимке по старинке и все заработало.

Выше я привел самый банальный пример, а во время реальной разработки корп. приложений подобных курьезов бывает море. Шанс просто так обновить клиентское ПО есть не всегда.

аватар: mrUlugbek
mrUlugbek
Дата: СР, 10/09/2014 - 15:49
Звание: Наблюдатель
Сообщений: 15

Спасибо за статью..
Можете пример показать как вашему методу сделать мастер детайл?

аватар: Dozent
Dozent
Дата: ВС, 14/09/2014 - 22:13
Звание: Энтузиаст
Сообщений: 115

Например?

аватар: Dozent
Dozent
Дата: СБ, 20/09/2014 - 20:00
Звание: Энтузиаст
Сообщений: 115

Вы про возможность встроить список одной задачи в карточку другой?

аватар: mrUlugbek
mrUlugbek
Дата: Втр, 23/09/2014 - 16:31
Звание: Наблюдатель
Сообщений: 15

Да примерно заполнение наподобие инвойса или накладной?
Там накладной + список товары
Или еще как увас там заполнение содрудника + его детали список место работы итд

аватар: Dozent
Dozent
Дата: ВС, 26/07/2015 - 05:12
Звание: Энтузиаст
Сообщений: 115

ой я уже не помню)) но попробую объясниться, я кинул на карточку PageControl, создал 3 закладки.
дальше пишу

Спойлер
procedure TfrmStaffCard.FormShow(Sender: TObject);
begin
inherited;
//эти табилцы переоткрываются, не связаны с вложеными списками
mtPost.Close;
mtPost.Open;
mtRank.Close;
mtRank.Open;
mtGuard.Close;
mtGuard.Open;
mtSquard.Close;
mtSquard.Open;
eGuardName.KeyValue := Null;
// отображаем данные в закладках
//уже форма список отпуском
//присваиваем парент нужную закладку
frmHoliDaysList.Parent := PageControl1.Pages[0];
//растягиваем
frmHoliDaysList.Align := alClient;
frmHoliDaysList.BorderStyle := bsToolWindow;
//убераем системные кнопки
frmHoliDaysList.BorderIcons := [];
//убераем панель поисков и фильтров
frmHoliDaysList.SearchPanel.Height := 5;
//устанавливаем внешний фильтр (непутать с внутреним, гдето в статьях про него писал)
//показываем только отпуски для сотрудника по которому открыта карточка
frmHoliDaysList.OuterFilter := Format(' Holidays.prsn_id = %s ', [ID]);
frmHoliDaysCard.PrsnID := ID;
//если это не вность созданая карточка, то по ней могут быть записи по отпускам и с ними можно работать,
// создать сразу отпуск или открыть уже имеющийся
if ID > 0 then
frmHoliDaysList.TopPanel.Visible := True
else
frmHoliDaysList.TopPanel.Visible := False;
frmHoliDaysList.Show;
//
frmInventoryList.Parent := PageControl1.Pages[1];
frmInventoryList.Align := alClient;
frmInventoryList.BorderStyle := bsToolWindow;
frmInventoryList.BorderIcons := [];
frmInventoryList.SearchPanel.Height := 5;
frmInventoryList.OuterFilter := Format(' Inventory.prsn_id = %s ', [ID]);
frmInventoryCard.PrsnID := ID;
if ID > 0 then
frmInventoryList.TopPanel.Visible := True
else
frmInventoryList.TopPanel.Visible := False;
frmInventoryList.Show;
//
frmSpecRankList.Parent := PageControl1.Pages[2];
frmSpecRankList.Align := alClient;
frmSpecRankList.BorderStyle := bsToolWindow;
frmSpecRankList.BorderIcons := [];
frmSpecRankList.SearchPanel.Height := 5;
frmSpecRankList.OuterFilter := Format(' Spec_rank.prsn_id = %s ', [ID]);
frmSpecRankCard.PrsnID := ID;
if ID > 0 then
frmSpecRankList.TopPanel.Visible := True
else
frmSpecRankList.TopPanel.Visible := False;
frmSpecRankList.Show;
end;
//////.....
procedure TfrmStaffCard.FormClose(Sender: TObject; var Action: TCloseAction);
begin
inherited;
//при закрытии обязательно нужно сделать видимыми вернии панели
// у вложеных списков иначе если вы заходите иоткрыть их как задачи
//верхней понеле может не быть
if not frmInventoryList.TopPanel.Visible then
frmInventoryList.TopPanel.Visible := True;
if not frmHoliDaysList.TopPanel.Visible then
frmHoliDaysList.TopPanel.Visible := True;
if not frmSpecRankList.TopPanel.Visible then
frmSpecRankList.TopPanel.Visible := True;
//ну и не забыть закрыть каждую вложеную задачу
//если всего этого не делать то могут быть глюки. связано с тем что
//мы работаем уже с созаным обьектом который в памяти
//если мы каждый раз создавали эти формы проблем бы этих не было
//НУ И ВСЁ ЭТО КОСТЫЛЬ ПРИЧЁМ ВАЩЕ ЖЁСТКИ!!! ВРЕМЕНИ НЕ БЫЛО)))
frmInventoryList.Close;
frmHoliDaysList.Close;
frmSpecRankList.Close;
end;

аватар: DrBlack
DrBlack
Дата: СР, 24/09/2014 - 03:42
Звание: Мастер
Сообщений: 1787

Цитировать
НУ И ВСЁ ЭТО КОСТЫЛЬ ПРИЧЁМ ВАЩЕ ЖЁСТКИ!!!

А инвалидные коляски есть? Laughing out loud