Опис алгоритму і функціонування програми

Модель програми, яка згенерована за допомогою системи моделювання Rational Rose 2000 показана на мал.3. На ньому показані всі класи і структури даних програми, їхня взаємодія і батьківські класи.

Створення програми складалося з двох важливих частин, опишемо кожну окремо.

Створення інтерфейсу

Програми створювалася в MS Visual Studio з використанням MFC. Проект був створений за допомогою майстра, що створив основне діалогове вікно (мал.4), на якому розмістили компонент TreeView (також за допомогою ClassWizard створюємо перемінну типу CTreeCtrl), який дозволяє переглядати ієрархічні дані.


Мал.3.
Модель програми

Були сформовані наступні класи: CINToApp (клас додатка), CINToDlg (клас головного діалогу), CAboutDlg (клас вікна «Про програму»).

Мал..4.

Для того, щоб при зміні розмірів вікна розмір TreeView теж мінявся, в клас CINToDlg додаємо наступний код:

#define BORDER 3

void CINToDlg::ResizeTreeView()

{

RECT m_Rect;

GetClientRect(&m_Rect);

m_MainTree.SetWindowPos(0, m_Rect.left + BORDER - 2, m_Rect.top, m_Rect.right - m_Rect.left - BORDER,

m_Rect.bottom - m_Rect.top - BORDER, 0);

}

void CINToDlg::OnSize(UINT nType, int cx, int cy)

{

CDialog::OnSize(nType, cx, cy);

ResizeTreeView();

}

Щоб змінити вид окремих елементів програми були створені нові класи: СMyButton (для зміни виду кнопок) і СFlatEdit (для зміни виду полів редагування). Дані класи є похідними від CButton і CEdit відповідно.

       
   
 

Крім головного діалогу в програмі присутні ще два: діалогові вікна «Додавання користувача» (мал.5) і «Розширені дані» (мал.6). Їхні класи відповідно CNewUserDialog і CPropertiesDlg.

Мал.5 Мал.6

У відповідності зі значками в списку користувачів можна взнати відразу про них деяку інформацію: внутрішній і зовнішній квадрат білі — звичайний користувач, внутрішній квадрат зелений — користувач є адміністратором, зовнішній квадрат червоний — користувач відключений.

З метою зручності в списку користувачів присутнє контекстне меню, у якому містяться можливі команди. Наступний код здійснює виведення меню:

void CINToDlg::OnRclickTree1(NMHDR* pNMHDR, LRESULT* pResult)

{

CMenu menu, *tmpmenu;

CWnd* parent = this;

CPoint pt = GetMessagePos();

// знаходимо головне вікно програми

while(parent->GetStyle() & WS_CHILD) parent = parent->GetParent();

menu.LoadMenu(IDR_USERTREEMENU);

tmpmenu = menu.GetSubMenu(1);

tmpmenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON, pt.x, pt.y, parent, NULL);

*pResult = 0;

}

Безпосередня реалізація функцій програми

Важливою особливістю програми є те, що для її повного функціонування необхідні привілеї адміністратора (якщо мережа знаходиться під контролем Windows NT (2000) Server тоді адміністратор повинний бути доменним). Тому при запуску програми йде перевірка повноважень:

BOOL UserIsNTAdmin()

{

BOOL bIsAdmin = FALSE;

HANDLE hProcess = 0, hAccessToken = 0;

UCHAR InfoBuffer[1024];

PTOKEN_GROUPS ptgGroups = (PTOKEN_GROUPS)InfoBuffer;

DWORD dwInfoBufferSize = 0;

PSID psidAdministrators = 0;

SID_IDENTIFIER_AUTHORITY siaNTAuthority = SECURITY_NT_AUTHORITY;

UINT uCount = 0;

hProcess = GetCurrentProcess();

if(!OpenProcessToken(hProcess, TOKEN_READ, &hAccessToken)) return FALSE;

bIsAdmin = GetTokenInformation(hAccessToken, TokenGroups, InfoBuffer, 1024, &dwInfoBufferSize);

CloseHandle(hAccessToken);

if(!bIsAdmin) return FALSE;

if(!AllocateAndInitializeSid(&siaNTAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_USER_RID_ADMINS, 0, 0, 0, 0, 0, 0, &psidAdministrators))

return FALSE;

bIsAdmin = FALSE;

for(uCount = 0; uCount < ptgGroups->GroupCount; uCount++)

{

if(EqualSid(psidAdministrators, ptgGroups->Groups

[uCount].Sid))

{

bIsAdmin = TRUE;

break;

}

}

FreeSid(psidAdministrators);

return bIsAdmin;

}

Ця функція єдиний раз спрацьовує при ініціалізації всієї програми і видає помилку, якщо користувач не має відповідних прав.

Кістяк програми складає клас Cusers, створений спеціально для функцій адміністрування. Саме об'єкти цього класу дозволяють здійснювати такі дії, як додавання і видалення користувача, видача про нього розширеної інформації. Крім цього він постійно зберігає не повну інформацію (ім'я і привілеї) про всіх користувачів системи. Розглянемо конструктор класу і функцію одержання інформації про існуючі користувачів:

CUsers::CUsers()

{

LPWKSTA_USER_INFO_1 s_WkstInfo;

lpUserList = NULL;

//Одержуємо ім'я сервера з якого був здійснений

//вхід користувача

NetWkstaUserGetInfo(NULL, 1, (LPBYTE *) &s_WkstInfo);

wcscpy(strServer, (LPCWSTR)s_WkstInfo->wkui1_logon_server);

NetApiBufferFree(s_WkstInfo);

// одержуємо список користувачів

GetUserList();

}

void CUsers::GetUserList()

{

DWORD i;

char name[MAXUSERNAME];

DWORD nCount, nCountRead;

LPUSER_INFO_1 m_Users, m_UsersTmp;

NetUserEnum(strServer, 1, FILTER_NORMAL_ACCOUNT, (LPBYTE*)&m_Users,

MAX_PREFERRED_LENGTH, &nCount, &nCountRead, NULL);

// якщо старі дані залишилися, то видаляємо їх

if(!lpUserList) delete[] lpUserList;

// і виділяємо пам'ять під обновлені дані

lpUserList = new _userstruct[nCountRead];

nUsers = nCountRead;

if((m_UsersTmp = m_Users)!= NULL)

{

for(i=0;i<nCountRead;i++)

{

memset(name, 0, MAXUSERNAME);

// переклад рядка UNICODE у ANSI

WideCharToMultiByte(CP_ACP,0,m_UsersTmp->usri1_name,

MAXUSERNAME, name, MAXUSERNAME, NULL, NULL);

lpUserList[i].userInfo = *m_UsersTmp;

lpUserList[i].name = name;

m_UsersTmp++;

}

}

//звільняємо пам'ять виділену викликом ф-ції //NetUserEnum

NetApiBufferFree(m_Users);

}

Проаналізувавши даний код, потрібно зупинитися на наступному моменті: функції виду Net... є частиною так називаного NetApi (чи LanMan Api) і використовують у своїй роботі замість звичайних рядків ANSI рядки UNICODE.

За замовчуванням Windows NT (2000) не використовує символів у форматі ANSI. Усі символьні і строкові дані обробляються усередині Windows у форматі UNICODE. У відмінності від ANSI, що використовує 8-бітне представлення символів, стандарт UNICODE кодує кожен символ 16-бітним числом. У стандарті UNICODE можна кодувати величезну кількість найрізноманітніших символів, у тому числі і спеціальних і не вхідних в англійський алфавіт. Багато програмістів усе ще воліють використовувати ANSI, і Windows NT (2000) надає їм таку можливість. Для підтримки двох різних систем кодування символів API містить у собі дві версії кожної з функцій, що працюють з рядками: одна з них орієнтована на UNICODE, друга — на ANSI.

Після виконання конструктора можна одержати ім'я користувача, передавши функції його номер у масиві lpUserList. Якщо переданий індекс буде за межами масиву, то функція закінчиться невдачею (поверне значення FALSE):

BOOL CUsers::GetName(DWORD n, CString& strName)

{

if(n >= nUsers) return FALSE;

strName = lpUserList[n].name;

return TRUE;

}

Перевантаживши ім'я даної функції, одержуємо метод, що крім імені користувача повертає ще деякі його атрибути (такі як привілеї і прапори, у форматі NetApi):

BOOL CUsers::GetName(DWORD n, CString& strName, DWORD& flags, DWORD& privilege)

{

if(n >= nUsers) return FALSE;

strName = lpUserList[n].name;

flags = lpUserList[n].userInfo.usri1_flags;

privilege = lpUserList[n].userInfo.usri1_priv;

return TRUE;

}

Щоб довідатися про такі моменти, як чи володіє користувач привілеями звичайного користувача, чи заблокований його обліковий запис, подивимося код, здійснюваний у місці виклику цього методу:

void CINToDlg::RefreshUsers()

{

// очищення TreeView

m_MainTree.DeleteAllItems();

DWORD i = 0, flags, priv;

CString strTemp;

TVINSERTSTRUCT tvInsert;

tvInsert.hParent = TVI_ROOT;

tvInsert.hInsertAfter = TVI_LAST;

tvInsert.item.mask = TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE;

tvInsert.item.state = TVIS_BOLD;

// виділимо кореневий елемент

tvInsert.item.stateMask = TVIS_BOLD;

// жирним шрифтом

tvInsert.item.pszText = "Users";

// у залежності від того чи виділений пункт “Users”

// у TreeView, він (пункт) змінює свою іконку

tvInsert.item.iImage = 0; tvInsert.item.iSelectedImage = 1;

tvInsert.item.lParam = -1;

hUserItem = m_MainTree.InsertItem(&tvInsert);

while(m_Users.GetName(i, strTemp, flags, priv))

{

AddUsr(strTemp, flags, priv, i);

i++;

}

}

#define ACCOUNT_ENABLED 2

#define ACCOUNT_DISABLED 3

#define ACCOUNT_ADMIN 4

void CINToDlg::AddUsr(LPCTSTR name, DWORD flags, DWORD priv, int i)

{

TVINSERTSTRUCT tvInsert;

int image;

image = (flags & UF_ACCOUNTDISABLE)? ACCOUNT_DISABLED: ACCOUNT_ENABLED;

// чи заблокований аккаунт?

// вибираємо іконку в залежності від прапорів і привілей

image = (priv & USER_PRIV_ADMIN)? ACCOUNT_ADMIN: image;

tvInsert.hParent = hUserItem;

// кореневий елемент “Users”

tvInsert.hInsertAfter = TVI_LAST;

tvInsert.item.mask = TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE;

tvInsert.item.state = 0;

tvInsert.item.stateMask = 0;

tvInsert.item.pszText = (char *)(LPCTSTR)name;

tvInsert.item.iImage = image;

tvInsert.item.iSelectedImage = image;

tvInsert.item.lParam = i;

m_MainTree.InsertItem(&tvInsert);

}

Варто звернути увагу на те, що в процесі додавання елементів у TreeView використовується таке поле - структура як lParam, який дозволяє зберегти додаткову інформацію про елемент (не тільки назву), у даному випадку — індекс у масиві lpUserList. Щоб у наслідку легше було довідатися, на якому елементі було зроблене натискання правою кнопкою миші. Нижче приведений текст програми є головною сполучною ланкою між графічним інтерфейсом і кістяком програми — класом CUsers.

void CINToDlg::OnTreeMenu(UINT nID)

{

HTREEITEM hItem;

CString name;

int i, code;

CNewUserDialog newUserDlg;

CPropertiesDlg propDlg;

// одержимо хэндл виділеного елемента

hItem = m_MainTree.GetSelectedItem();

i = m_MainTree.GetItemData(hItem);

// дивимося який пункт меню обраний

switch(nID)

{

// видалення користувача

case ID_RIGHTBUTTON_DELETEUSER:

if(i >= 0 && m_Users.GetName(i,name))

{

name.Insert(0, "Deleting user ");

code = MessageBox("Are you sure?", name, MB_YESNO | MB_ICONASTERISK);

// упевнимося що видалення повинне відбутися

if(code == IDYES)

{

// якщо індекс за межами видалення не буде

if(!m_Users.DeleteUser(i))

{

MessageBox("User not deleted");

break;

}

RefreshUsers();

m_MainTree.Expand(hUserItem, TVE_EXPAND);

}

}

else MessageBox("Can't delete this item");

break;

// додавання користувача

case ID_RIGHTBUTTON_ADDNEWUSER:

// покажемо діалог додавання

newUserDlg.DoModal();

// перевірка, що натиснуто кнопку “OK”

if(!newUserDlg.strName.IsEmpty())

{

// додамо користувача, беручи дані

// з даних діалогу додавання

if(!m_Users.AddUser(newUserDlg.strName, newUserDlg.strPassword))

{

MessageBox("User not added");

break;

}

RefreshUsers();

// обновимо список

m_MainTree.Expand(hUserItem, TVE_EXPAND);

}

break;

// розширена інформація

case ID_RIGHTBUTTON_SHOWEXTENDEDINFO:

// у діалозі расш. властивостей ця структура

// заповнюється не їм самим а зараз перед появою.

// тому самі очищаємо її від попередніх даних

memset(&propDlg.userInfo, 0, sizeof(USERINFO));

// елемент нам не підходить

if(i < 0) break;

if(!m_Users.GetUserFullInfo(i, &propDlg.userInfo))

{

MessageBox("Can't get user info");

break;

}

propDlg.DoModal();

break;

}

}

Тут ми бачимо використання методів класу CUsers. Розглянемо їх. Функція AddUser робить створення нового облікового запису в систему. Параметри її наступні: ім'я користувача (повинне бути унікальним) і пароль (повинний відповідати вимогам системи). Треба зауважити, щоб не було непорозумінь у вікні введення пароля нового користувача він уводиться двічі:

BOOL CUsers::AddUser(LPCTSTR name, LPCTSTR password)

{

USER_INFO_1 usrInfo;

WCHAR aName[MAXUSERNAME], aPass[MAXUSERPASSWORD];

DWORD dwError;

// переклад імені і пароля в UNICODE

MultiByteToWideChar(CP_ACP, 0, name, MAXUSERNAME, aName, MAXUSERNAME);

MultiByteToWideChar(CP_ACP, 0, password, MAXUSERPASSWORD, aPass, MAXUSERPASSWORD);

memset(&usrInfo, 0, sizeof(USER_INFO_1));

usrInfo.usri1_comment = L"Maded by iNTo";

// коментар

usrInfo.usri1_flags = UF_SCRIPT;

usrInfo.usri1_home_dir = NULL;

usrInfo.usri1_name = aName;

// ім'я

usrInfo.usri1_password = aPass;

// пароль

usrInfo.usri1_priv = USER_PRIV_USER;

// привілеї

if(NetUserAdd(strServer,1,(LPBYTE)&usrInfo, &dwError)==NERR_Success)

{

GetUserList(); // обновляємо масив

return TRUE; // усе нормально

}

return FALSE; // помилка

}

Функція DeleteUser видаляє вже існуючий обліковий запис. Параметр усього один — індекс імені користувача в lpUserList.

BOOL CUsers::DeleteUser(DWORD n)

{

WCHAR aName[MAXUSERNAME];

// індекс у межах масиву?

if(n >= nUsers) return FALSE;

// переклад імені в UNICODE

MultiByteToWideChar(CP_ACP, 0, lpUserList[n].name, MAXUSERNAME, aName, MAXUSERNAME);

if(NetUserDel(strServer, aName) == NERR_Success)

{

GetUserList(); // обновляємо масив

return TRUE; // усе нормально

}

return FALSE; // помилка

}

Функція GetUserFullInfo одержує розширені дані про користувача (ім'я, повне ім'я, коментар, привілеї, блокування і групи, до яких обліковий запис належить), що зберігаються в структуру даних розроблену спеціально для неї.

BOOL CUsers::GetUserFullInfo(DWORD n, USERINFO* usrInfo)

{

DWORD dwLevel = 2, dwRead, dwAll, i;

LPUSER_INFO_2 wInfo = NULL;

char name[MAXCHARS];

NET_API_STATUS nStatus;

LPGROUP_USERS_INFO_0 lpGroups, lpTmp;

// індекс у межах масиву?

if(n >= nUsers) return FALSE;

// заповнимо полючи даними які вже є

strcpy(usrInfo->name, lpUserList[n].name);

nStatus = NetUserGetInfo(strServer, lpUserList[n].userInfo.usri1_name, dwLevel, (LPBYTE *) &wInfo);

if(nStatus!= NERR_Success) return FALSE;

// помилка

memset(name, 0, MAXCHARS);

WideCharToMultiByte(CP_ACP, 0, wInfo->usri2_full_name, MAXCHARS, name, MAXCHARS, NULL, NULL);

strcpy(usrInfo->fullName, name);

memset(name, 0, MAXCHARS);

WideCharToMultiByte(CP_ACP, 0, wInfo->usri2_comment, MAXCHARS, name, MAXCHARS, NULL, NULL);

strcpy(usrInfo->comment, name);

// заповнюємо поля

usrInfo->isAdmin = (wInfo->usri2_priv & USER_PRIV_ADMIN)? TRUE: FALSE;

usrInfo->isGuest = (wInfo->usri2_priv & USER_PRIV_GUEST)? TRUE: FALSE;

usrInfo->isDisabled = (wInfo->usri2_flags & UF_ACCOUNTDISABLE)? TRUE: FALSE;

// перелічимо всі групи до яких користувач відноситься

nStatus = NetUserGetGroups(strServer, lpUserList[n].userInfo.usri1_name, 0, (LPBYTE *) &lpGroups,

MAX_PREFERRED_LENGTH, &dwRead, &dwAll);

// якщо не вийшло, завершуємо роботу функції

if((nStatus!= NERR_Success) || (dwRead!= dwAll)) return FALSE;

if((lpTmp = lpGroups)!= NULL)

{

for(i=0;i<dwRead;i++)

{

if(i >= MAXUSERGROUPS) break;

memset(name, 0, MAXCHARS);

WideCharToMultiByte(CP_ACP, 0, lpTmp->grui0_name, MAXCHARS, name, MAXCHARS, NULL, NULL);

// зберігаємо ім'я групи

strcpy(usrInfo->groups[i], name);

lpTmp++;

}

}

// зберігаємо кількість груп

usrInfo->nGroups = dwRead;

// вивільняємо пам'ять виділену системою

NetApiBufferFree(wInfo);

NetApiBufferFree(lpGroups);

return TRUE;

}


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: