Підтвердити ім'я користувача та пароль для Active Directory?


526

Як я можу перевірити ім'я користувача та пароль щодо Active Directory? Я просто хочу перевірити, чи правильно ім’я користувача та пароль.

Відповіді:


642

Якщо ви працюєте в .NET 3.5 або новіших версіях, ви можете використовувати System.DirectoryServices.AccountManagementпростір імен і легко перевірити свої облікові дані:

// create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAIN"))
{
    // validate the credentials
    bool isValid = pc.ValidateCredentials("myuser", "mypassword");
}

Це просто, це надійно, це 100% C # керований код на вашому кінці - що ще ви можете запитати? :-)

Про це читайте тут:

Оновлення:

Як зазначено в цьому іншому запитанні "SO" (та його відповідях) , цей виклик може повернутися Trueдо старих паролів користувача. Просто пам’ятайте про таку поведінку і не надто дивуйтеся, якщо це станеться :-) (спасибі @MikeGledhill за те, що вказав на це!)


36
У своєму домені я повинен був вказати pc.ValidateCredentials ("myuser", "mypassword", ContextOptions.Negotiate) або я отримав System.DirectoryServices.Protocols.DirectoryOperationException: Сервер не може обробляти запити каталогів.
Алекс Пек

12
Якщо термін дії пароля минув або облікові записи вимкнено, ValidateCredentials поверне помилкові. На жаль, він не говорить вам, чому він повертається помилково (що шкода, оскільки це означає, що я не можу зробити щось розумне, як перенаправити користувача, щоб змінити свій пароль).
Кріс Дж

64
Також остерігайтеся облікового запису "Гість" - якщо ввімкнено гостьовий обліковий запис на рівні домену, ValidateCredentials повертає справжнє значення, якщо ви надаєте його неіснуючому користувачеві. В результаті ви можете зателефонувати, UserPrinciple.FindByIdentityщоб переконатися, чи є передане в ідентифікаторі користувача першим.
Кріс Дж

7
@AlexPeck: причиною, чому вам довелося це зробити (як я), було те, що .NET використовує такі технології за замовчуванням: LDAP + SSL, Kerberos, а потім RPC. Я підозрюю, що RPC вимкнено у вашій мережі (добре!), І Kerberos насправді не використовує .NET, якщо ви прямо не скажете це за допомогою ContextOptions.Negotiate.
Бретт Веенстра

5
Майте на увазі, що якщо користувач змінить свій пароль Active Directory, цей фрагмент коду буде продовжувати радісно ідентифікувати користувача за допомогою свого старого пароля AD. Так, справді. Читайте тут: stackoverflow.com/questions/8949501/…
Майк Гледхілл

70

Ми робимо це в нашій Інтранеті

Ви повинні використовувати System.DirectoryServices;

Ось кишки коду

using (DirectoryEntry adsEntry = new DirectoryEntry(path, strAccountId, strPassword))
{
    using (DirectorySearcher adsSearcher = new DirectorySearcher(adsEntry))
    {
        //adsSearcher.Filter = "(&(objectClass=user)(objectCategory=person))";
        adsSearcher.Filter = "(sAMAccountName=" + strAccountId + ")";

        try
        {
            SearchResult adsSearchResult = adsSearcher.FindOne();
            bSucceeded = true;

            strAuthenticatedBy = "Active Directory";
            strError = "User has been authenticated by Active Directory.";
        }
        catch (Exception ex)
        {
            // Failed to authenticate. Most likely it is caused by unknown user
            // id or bad strPassword.
            strError = ex.Message;
        }
        finally
        {
            adsEntry.Close();
        }
    }
}

9
Що ви ставите на «стежку»? Назва домену? Ім'я сервера? Шлях LDAP до домену? Шлях LDAP до сервера?
Ян Бойд

3
Відповідь1: Ні, ми запускаємо це як веб-сервіс, щоб його можна було викликати з декількох місць у головному веб-додатку. Відповідь2: Шлях містить інформацію про LDAP ... LDAP: // DC = domainname1, DC = domainname2, DC = com
DiningPhilanderer

3
Здавалося б, це може дозволити ін'єкцію ЛДАП. Ви можете переконатися, що вам потрібно вийти чи видалити дужки у strAccountId
Brain2000

Чи означає це, що strPasswordзберігається в LDAP у простому тексті?
Метт Кокай

15
Там ніколи не повинно бути необхідності явного виклику Close()на usingзмінному.
Nyerguds

62

У кількох представлених тут рішеннях відсутня можливість розмежувати неправильний користувач / пароль та пароль, який потрібно змінити. Це можна зробити наступним чином:

using System;
using System.DirectoryServices.Protocols;
using System.Net;

namespace ProtocolTest
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                LdapConnection connection = new LdapConnection("ldap.fabrikam.com");
                NetworkCredential credential = new NetworkCredential("user", "password");
                connection.Credential = credential;
                connection.Bind();
                Console.WriteLine("logged in");
            }
            catch (LdapException lexc)
            {
                String error = lexc.ServerErrorMessage;
                Console.WriteLine(lexc);
            }
            catch (Exception exc)
            {
                Console.WriteLine(exc);
            }
        }
    }
}

Якщо пароль користувача неправильний або він не існує, помилка буде міститись

"8009030C: LdapErr: DSID-0C0904DC, коментар: помилка AcceptSecurityContext, дані 52e, v1db1",

якщо пароль користувачів потрібно змінити, він буде містити

"8009030C: LdapErr: DSID-0C0904DC, коментар: AcceptSecurityContext помилка, дані 773, v1db1"

Значення lexc.ServerErrorMessageданих - це шістнадцяткове представлення коду помилки Win32. Це ті самі коди помилок, які повертаються, інакше викликаючи виклик API Win32 LogonUser. У наведеному нижче списку узагальнено діапазон загальних значень із шістнадцятковими та десятковими значеннями:

525 user not found ​(1317)
52e invalid credentials ​(1326)
530 not permitted to logon at this time (1328)
531 not permitted to logon at this workstation (1329)
532 password expired ​(1330)
533 account disabled ​(1331) 
701 account expired ​(1793)
773 user must reset password (1907)
775 user account locked (1909)

2
На жаль, деякі установки AD не повертають код помилки LDAP, що означає, що це рішення не працюватиме.
Søren Mors

4
Не забудьте додати кілька посилань на проект: System.DirectoryServicesіSystem.DirectoryServices.Protocols
TomXP411

3
Моє запитання, однак, таке: як отримати ім'я сервера LDAP? Якщо ви пишете портативну програму, ви не можете сподіватися, що користувач дізнається чи потребує введення імен серверів AD у кожній мережі.
TomXP411

1
У мене є користувачі, які обмежуються входом на конкретні робочі станції; як вказати робочу станцію, для якої я пробую ввійти? (workstation1 не вдасться з даними 531, workstation2 буде добре працювати, наприклад)
akohlsmith

1
Я відчуваю себе дивно, бо не думаю, що ти отримуєш достатньо кредиту. Це повністю керований метод без проблем виклику Win32 API, щоб визначити, чи "користувач повинен скинути пароль", що очевидно жоден з інших досягнутих відповідей. Чи є якась лазівка ​​в цьому методі, що спричиняє низьку оцінку? хм ...
Ліонет Чен

34

дуже просте рішення за допомогою DirectoryServices:

using System.DirectoryServices;

//srvr = ldap server, e.g. LDAP://domain.com
//usr = user name
//pwd = user password
public bool IsAuthenticated(string srvr, string usr, string pwd)
{
    bool authenticated = false;

    try
    {
        DirectoryEntry entry = new DirectoryEntry(srvr, usr, pwd);
        object nativeObject = entry.NativeObject;
        authenticated = true;
    }
    catch (DirectoryServicesCOMException cex)
    {
        //not authenticated; reason why is in cex
    }
    catch (Exception ex)
    {
        //not authenticated due to some other exception [this is optional]
    }

    return authenticated;
}

доступ до NativeObject потрібен для виявлення поганого користувача / пароля


4
Цей код поганий, оскільки він також робить перевірку авторизації (перевірте, чи дозволено користувачеві читати інформацію про активну директорію). Ім'я користувача та пароль можуть бути дійсними, але користувачеві заборонено читати інформацію - і отримати виняток. Іншими словами, ви можете мати дійсне ім’я користувача та пароль, але все ж отримуєте виняток.
Ян Бойд

2
я фактично перебуваю у запиті про власний еквівалент PrincipleContext- який існує лише в .NET 3.5. Але якщо ви використовуєте .NET 3.5 або новішу версію, вам слід скористатисяPrincipleContext
Ian Boyd

28

На жаль, не існує "простого" способу перевірки облікових даних користувачів в AD.

З кожним методом, представленим до цього часу, ви можете отримати помилково негативний результат: Записи користувача будуть дійсними, проте AD поверне помилкові за певних обставин:

  • Користувачеві потрібно змінити пароль при наступному вході.
  • Термін дії користувача закінчився.

ActiveDirectory не дозволить вам використовувати LDAP, щоб визначити, чи недійсний пароль через те, що користувач повинен змінити пароль або термін дії його пароля.

Щоб визначити зміну пароля чи термін дії пароля, ви можете зателефонувати на Win32: LogonUser () та перевірити код помилки Windows на наступні 2 константи:

  • ERROR_PASSWORD_MUST_CHANGE = 1907
  • ERROR_PASSWORD_EXPIRED = 1330

1
Чи можу я запитати, де у вас девініції для Expired and Must_Change ... Не знайшли їх ніде, але тут :)
mabstrei


Дякую. Я намагався з'ясувати, як моя перевірка весь час повертала помилку. Це було тому, що користувачеві потрібно змінити свій пароль.
Deise Vicentin

22

Напевно, найпростіший спосіб - це PInvoke LogonUser Win32 API.eg

http://www.pinvoke.net/default.aspx/advapi32/LogonUser.html

Тут посилання на MSDN ...

http://msdn.microsoft.com/en-us/library/aa378184.aspx

Однозначно хочу використовувати тип входу

LOGON32_LOGON_NETWORK (3)

Це створює лише легкий маркер - ідеально підходить для перевірки AuthN. (інші типи можна використовувати для створення інтерактивних сесій тощо)


Як зазначає @Alan, API LogonUser має багато корисних рис, що виходять за виклик System.DirectoryServices.
stephbu

3
@cciotti: Ні, це неправильно. НАЙКРАЩИЙ спосіб правильної аутентифікації когось - це використовувати LogonUserAPI як запис @stephbu. Усі інші способи, описані в цій публікації, НЕ РОБОТИ 100%. Однак лише зауважте, я вважаю, що вам потрібно приєднатися до домену, щоб подзвонити LogonUser.
Алан

@Alan для створення облікових даних ви повинні мати можливість підключитися до домену, передаючи дійсний обліковий запис домену. Однак я впевнений, що ваша машина не обов'язково повинна бути членом домену.
stephbu

2
LogonUserAPI вимагає від користувача мати Діяти як частина операційної системи privelage; що не щось, що отримують користувачі - і не те, що ви хочете надавати кожному користувачеві в організації. ( msdn.microsoft.com/en-us/library/aa378184(v=vs.85).aspx )
Ian Boyd

1
LogonUser потребує лише Закону як частини операційної системи для Windows 2000 і нижче відповідно до support.microsoft.com/kb/180548 ... Це виглядає чисто для сервера 2003 та новіших версій .
Кріс Дж

18

Повноцінним рішенням .Net є використання класів з простору імен System.DirectoryServices. Вони дозволяють безпосередньо запитувати сервер AD. Ось невеликий зразок, який би це зробив:

using (DirectoryEntry entry = new DirectoryEntry())
{
    entry.Username = "here goes the username you want to validate";
    entry.Password = "here goes the password";

    DirectorySearcher searcher = new DirectorySearcher(entry);

    searcher.Filter = "(objectclass=user)";

    try
    {
        searcher.FindOne();
    }
    catch (COMException ex)
    {
        if (ex.ErrorCode == -2147023570)
        {
            // Login or password is incorrect
        }
    }
}

// FindOne() didn't throw, the credentials are correct

Цей код безпосередньо підключається до сервера AD, використовуючи надані облікові дані. Якщо облікові дані недійсні, searchcher.FindOne () видасть виняток. ErrorCode - це той, що відповідає помилці COM "недійсне ім'я користувача / пароль".

Не потрібно запускати код як користувач AD. Насправді я успішно використовую її для запиту інформації на сервері AD, від клієнта за межами домену!


як щодо типів аутентифікації? я думаю, ти це забув у своєму коді вище. :-) за замовчуванням DirectoryEntry.AuthenticationType встановлено на Захищене право? цей код не працюватиме на LDAP, які не захищені (можливо, анонімний або жоден). я з цим правильно?
jerbersoft

Нижче на запит сервера AD є те, що у вас є дозвіл на запит сервера AD. Ваш обліковий запис може бути дійсним, але якщо ви не маєте дозволу на запит AD, ви отримаєте помилку. Ось чому було створено так зване Fast Bind ; ви підтверджуєте облікові дані, не надаючи можливість користувачеві щось робити.
Ян Бойд

2
Чи це не дозволить комусь пройти у випадку, якщо COMException буде викинуто з будь-якої іншої причини до перевірки облікових даних?
Стефан Пол Ноак

11

Ще один виклик .NET для швидкої автентифікації облікових даних LDAP:

using System.DirectoryServices;

using(var DE = new DirectoryEntry(path, username, password)
{
    try
    {
        DE.RefreshCache(); // This will force credentials validation
    }
    catch (COMException ex)
    {
        // Validation failed - handle how you want
    }
}

Це єдине рішення, яке працювало на мене, і використання PrincipalContext не працювало для мене.
Даніель

PrincipalContext недійсний для безпечного з'єднання LDAP (він же LDAPS, який використовує порт 636
Kiquenet

10

Спробуйте цей код (ПРИМІТКА. Повідомляється, що він не працює на сервері Windows 2000)

#region NTLogonUser
#region Direct OS LogonUser Code
[DllImport( "advapi32.dll")]
private static extern bool LogonUser(String lpszUsername, 
    String lpszDomain, String lpszPassword, int dwLogonType, 
    int dwLogonProvider, out int phToken);

[DllImport("Kernel32.dll")]
private static extern int GetLastError();

public static bool LogOnXP(String sDomain, String sUser, String sPassword)
{
   int token1, ret;
   int attmpts = 0;

   bool LoggedOn = false;

   while (!LoggedOn && attmpts < 2)
   {
      LoggedOn= LogonUser(sUser, sDomain, sPassword, 3, 0, out token1);
      if (LoggedOn) return (true);
      else
      {
         switch (ret = GetLastError())
         {
            case (126): ; 
               if (attmpts++ > 2)
                  throw new LogonException(
                      "Specified module could not be found. error code: " + 
                      ret.ToString());
               break;

            case (1314): 
               throw new LogonException(
                  "Specified module could not be found. error code: " + 
                      ret.ToString());

            case (1326): 
               // edited out based on comment
               //  throw new LogonException(
               //   "Unknown user name or bad password.");
            return false;

            default: 
               throw new LogonException(
                  "Unexpected Logon Failure. Contact Administrator");
              }
          }
       }
   return(false);
}
#endregion Direct Logon Code
#endregion NTLogonUser

окрім того, вам потрібно буде створити власний спеціальний виняток для "LogonException"


Не використовуйте обробку винятків для повернення інформації з методу. "Невідоме ім'я користувача або неправильний пароль" не є винятковим, це стандартна поведінка для LogonUser. Просто поверніть хибне.
Треб

так ... це був порт зі старої бібліотеки VB6 ... написаний 2003-го року ... (коли .Net вперше вийшов)
Чарльз Бретана

Якщо працює на Windows 2000, цей код не працюватиме ( support.microsoft.com/kb/180548 )
Ian Boyd

1
Переосмислення цього. Очікувана поведінка користувача входу в систему, його мета - ввійти в систему . Якщо він не в змозі виконати це завдання, це IS виняток. Насправді метод повинен повернути недійсність, а не булеву. Плюс, якщо ви щойно повернули булевий метод, споживач методу не має можливості повідомити користувача про те, що була причиною відмови.
Чарльз Бретана

5

Якщо ви застрягли з .NET 2.0 та керованим кодом, ось ще один спосіб, який працює з локальними та доменними обліковими записами:

using System;
using System.Collections.Generic;
using System.Text;
using System.Security;
using System.Diagnostics;

static public bool Validate(string domain, string username, string password)
{
    try
    {
        Process proc = new Process();
        proc.StartInfo = new ProcessStartInfo()
        {
            FileName = "no_matter.xyz",
            CreateNoWindow = true,
            WindowStyle = ProcessWindowStyle.Hidden,
            WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
            UseShellExecute = false,
            RedirectStandardError = true,
            RedirectStandardOutput = true,
            RedirectStandardInput = true,
            LoadUserProfile = true,
            Domain = String.IsNullOrEmpty(domain) ? "" : domain,
            UserName = username,
            Password = Credentials.ToSecureString(password)
        };
        proc.Start();
        proc.WaitForExit();
    }
    catch (System.ComponentModel.Win32Exception ex)
    {
        switch (ex.NativeErrorCode)
        {
            case 1326: return false;
            case 2: return true;
            default: throw ex;
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }

    return false;
}   

Добре працює з локальними обліковими записами машини, запускаючи сценарій
eka808

BTW, цей метод необхідний, щоб зробити це загальнодоступним статичним SecureString ToSecureString (рядок PwString) {char [] PasswordChars = PwString.ToCharArray (); SecureString Password = новий SecureString (); foreach (char c у PasswordChars) Password.AppendChar (c); Foo ProcessStartInfo = новий ProcessStartInfo (); foo.Password = Пароль; повернути foo.Password; }
eka808

Навпаки, так чи інакше слід використовувати SecureString для паролів. WPF PasswordBox підтримує його.
Стівен Дрю

5

Аутентифікація Windows може не вдатися з різних причин: неправильне ім’я користувача або пароль, заблокований обліковий запис, пароль із минулим терміном тощо. Щоб розрізняти ці помилки, викличте функцію API LogonUser за допомогою P / Invoke та перевірте код помилки, якщо функція повертається false:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

using Microsoft.Win32.SafeHandles;

public static class Win32Authentication
{
    private class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private SafeTokenHandle() // called by P/Invoke
            : base(true)
        {
        }

        protected override bool ReleaseHandle()
        {
            return CloseHandle(this.handle);
        }
    }

    private enum LogonType : uint
    {
        Network = 3, // LOGON32_LOGON_NETWORK
    }

    private enum LogonProvider : uint
    {
        WinNT50 = 3, // LOGON32_PROVIDER_WINNT50
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr handle);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool LogonUser(
        string userName, string domain, string password,
        LogonType logonType, LogonProvider logonProvider,
        out SafeTokenHandle token);

    public static void AuthenticateUser(string userName, string password)
    {
        string domain = null;
        string[] parts = userName.Split('\\');
        if (parts.Length == 2)
        {
            domain = parts[0];
            userName = parts[1];
        }

        SafeTokenHandle token;
        if (LogonUser(userName, domain, password, LogonType.Network, LogonProvider.WinNT50, out token))
            token.Dispose();
        else
            throw new Win32Exception(); // calls Marshal.GetLastWin32Error()
    }
}

Використання зразка:

try
{
    Win32Authentication.AuthenticateUser("EXAMPLE\\user", "P@ssw0rd");
    // Or: Win32Authentication.AuthenticateUser("user@example.com", "P@ssw0rd");
}
catch (Win32Exception ex)
{
    switch (ex.NativeErrorCode)
    {
        case 1326: // ERROR_LOGON_FAILURE (incorrect user name or password)
            // ...
        case 1327: // ERROR_ACCOUNT_RESTRICTION
            // ...
        case 1330: // ERROR_PASSWORD_EXPIRED
            // ...
        case 1331: // ERROR_ACCOUNT_DISABLED
            // ...
        case 1907: // ERROR_PASSWORD_MUST_CHANGE
            // ...
        case 1909: // ERROR_ACCOUNT_LOCKED_OUT
            // ...
        default: // Other
            break;
    }
}

Примітка: LogonUser вимагає довірчих відносин з доменом, проти якого ви перевіряєте.


Ви можете пояснити, чому ваша відповідь краща за найвищу відповідь?
Мохаммед Алі

1
@MohammadAli: Якщо вам потрібно знати, чому перевірка облікових даних не вдалася (неправильні облікові дані, заблокований обліковий запис, пароль з минулим терміном тощо), функція API LogonUser скаже вам. На відміну від цього, метод PrincipalContext.ValidateCredentials (відповідно до коментарів до відповіді marc_s) не буде; у всіх цих випадках він повертає помилкове значення. З іншого боку, LogonUser вимагає довірчих відносин з доменом, але PrincipalContext.ValidateCredentials (я думаю) цього не робить.
Майкл Лю

2

Моя проста функція

 private bool IsValidActiveDirectoryUser(string activeDirectoryServerDomain, string username, string password)
    {
        try
        {
            DirectoryEntry de = new DirectoryEntry("LDAP://" + activeDirectoryServerDomain, username + "@" + activeDirectoryServerDomain, password, AuthenticationTypes.Secure);
            DirectorySearcher ds = new DirectorySearcher(de);
            ds.FindOne();
            return true;
        }
        catch //(Exception ex)
        {
            return false;
        }
    }

1

Ось моє повне рішення для аутентифікації для вашої довідки.

Спочатку додайте наступні чотири посилання

 using System.DirectoryServices;
 using System.DirectoryServices.Protocols;
 using System.DirectoryServices.AccountManagement;
 using System.Net; 

private void AuthUser() { 


      try{
            string Uid = "USER_NAME";
            string Pass = "PASSWORD";
            if (Uid == "")
            {
                MessageBox.Show("Username cannot be null");
            }
            else if (Pass == "")
            {
                MessageBox.Show("Password cannot be null");
            }
            else
            {
                LdapConnection connection = new LdapConnection("YOUR DOMAIN");
                NetworkCredential credential = new NetworkCredential(Uid, Pass);
                connection.Credential = credential;
                connection.Bind();

                // after authenticate Loading user details to data table
                PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
                UserPrincipal user = UserPrincipal.FindByIdentity(ctx, Uid);
                DirectoryEntry up_User = (DirectoryEntry)user.GetUnderlyingObject();
                DirectorySearcher deSearch = new DirectorySearcher(up_User);
                SearchResultCollection results = deSearch.FindAll();
                ResultPropertyCollection rpc = results[0].Properties;
                DataTable dt = new DataTable();
                DataRow toInsert = dt.NewRow();
                dt.Rows.InsertAt(toInsert, 0);

                foreach (string rp in rpc.PropertyNames)
                {
                    if (rpc[rp][0].ToString() != "System.Byte[]")
                    {
                        dt.Columns.Add(rp.ToString(), typeof(System.String));

                        foreach (DataRow row in dt.Rows)
                        {
                            row[rp.ToString()] = rpc[rp][0].ToString();
                        }

                    }  
                }
             //You can load data to grid view and see for reference only
                 dataGridView1.DataSource = dt;


            }
        } //Error Handling part
        catch (LdapException lexc)
        {
            String error = lexc.ServerErrorMessage;
            string pp = error.Substring(76, 4);
            string ppp = pp.Trim();

            if ("52e" == ppp)
            {
                MessageBox.Show("Invalid Username or password, contact ADA Team");
            }
            if ("775​" == ppp)
            {
                MessageBox.Show("User account locked, contact ADA Team");
            }
            if ("525​" == ppp)
            {
                MessageBox.Show("User not found, contact ADA Team");
            }
            if ("530" == ppp)
            {
                MessageBox.Show("Not permitted to logon at this time, contact ADA Team");
            }
            if ("531" == ppp)
            {
                MessageBox.Show("Not permitted to logon at this workstation, contact ADA Team");
            }
            if ("532" == ppp)
            {
                MessageBox.Show("Password expired, contact ADA Team");
            }
            if ("533​" == ppp)
            {
                MessageBox.Show("Account disabled, contact ADA Team");
            }
            if ("533​" == ppp)
            {
                MessageBox.Show("Account disabled, contact ADA Team");
            }



        } //common error handling
        catch (Exception exc)
        {
            MessageBox.Show("Invalid Username or password, contact ADA Team");

        }

        finally {
            tbUID.Text = "";
            tbPass.Text = "";

        }
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.