Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Прочее";
Текущий архив: 2014.09.28;
Скачать: [xml.tar.bz2];

Вниз

Доменная авторизация   Найти похожие ветки 

 
Mikki   (2013-10-01 18:16) [0]

Имеем Дельфи клиент + БД.
В БД хранится список пользователей, своя таблица.

Хочется прикрутить windows авторизацию. То есть, если компьютер в домене, запуск из под пользователя - чтобы программа не спрашивала логин пароль.

Первоначальная мысль - в своей таблице также записать имя пользователя доменного, например: mydomen\myuser и если пройдет авторизация для этого доменного юзера - присвоить пользователю права, соответствующие id вычисленного пользователя.

Но вот дальше.. куда копать? Что передавать с клиента, как проверять валидность на сервере.. и в каком порядке вообще осуществлять коннект.. наверняка у кого то есть опыт - поделитесь плиз


 
brother ©   (2013-10-01 18:35) [1]

> Что передавать с клиента

id, имя mydomen\myuser, хэш этих данных...

> как проверять валидность на сервере

=

> и в каком порядке вообще осуществлять коннект

это о чем?


 
Необычный порошок   (2013-10-01 18:55) [2]

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

и зачем раздавать права в вручную, если юзер прошел доменную авторизацию и права можно дать доменным ролям/группам/пользователям?


 
brother ©   (2013-10-01 19:59) [3]

я так понимаю, идет речь о правах на доступа к таблицам БД


 
Mikki   (2013-10-01 20:15) [4]


> id, имя mydomen\myuser, хэш этих данных..

что такое Id?

Хм, допустим я передам mydomen\myuser, но что мешает передать имя любого пользователя?


> зачем доменная, если права будут раздаваться вручную?

чтобы пользователь не вводил логин пароль


> и зачем раздавать права в вручную, если юзер прошел доменную
> авторизацию и права можно дать доменным ролям/группам/пользователям?

это уже другой вопрос. В любом случае, своя система прав в программе есть. Связывать её с доменными правами - следующий, возможно, этап. Пока хотя бы авторизацию понять.


 
vuk ©   (2013-10-01 20:44) [5]

to Mikki   (01.10.13 18:16):

> Хочется прикрутить windows авторизацию.

Что за БД-то хоть? А то кой-где такая автоматическая авторизация уже встроена.


 
Необычный Порошок   (2013-10-01 20:45) [6]

чтобы пользователь не вводил логин пароль

Он и не будет вводить пароль если будет доменная.

это уже другой вопрос. В любом случае, своя система прав в программе есть. Связывать её с доменными правами - следующий, возможно, этап.

своя система прав лишняя при доменной авторизации.

Пока хотя бы авторизацию понять.
Для начала надо дать понять аудитории что у тебя за сервер.
Так как ответ на твой сокровенный вопрос напрямую зависит от этого.


 
vuk ©   (2013-10-01 20:52) [7]

to Необычный Порошок   (01.10.13 20:45) [6]:

> своя система прав лишняя при доменной авторизации.

Не лишняя. Своя система прав может реализовывать отличную от реализованной в сервере БД логику. К примеру, построчные права и т.д.


 
Необычный Порошок   (2013-10-01 20:58) [8]

да да.
именно ради rls автор и задумал смешать свое и доменное.
а нам всем только показалось что главная причина в том, что он не хочет чтобы юзер вводил пароль.


 
vuk ©   (2013-10-01 21:03) [9]

Причин для написания своей системы безопасности может быть много. И это вообще мало зависит от того, какая модель аутентификации используется для соединения с сервером.


 
Необычный Порошок   (2013-10-01 21:09) [10]

у него все именно так и будет. главное верить.


 
vuk ©   (2013-10-01 21:14) [11]

to Необычный Порошок   (01.10.13 21:09) [10]

> именно так и будет

Так - это как?


 
Необычный Порошок   (2013-10-01 21:17) [12]

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

будет заморочен вопросом роу левел секьюрити


 
vuk ©   (2013-10-01 21:28) [13]

Еще раз повторяю, причины могут быть и другие, помимо rls.


 
Необычный Порошок   (2013-10-01 21:32) [14]

яж разве спорю.
например желание чтобы юзер не вводил пароль - чем не и другая причина


 
Mikki   (2013-10-01 22:19) [15]

vuk, хотелось бы понять алгоритм / действия, не опираясь на базу MS SQL.

Конечно, хотелось бы опыта человека, который прикручивал AD авторизацию в приложении windows на delphi.

По сути, как я понимаю, нужно на сервер отправить некую штуку, основанную на логине/пароле доменного юзера, а сервер эту штуку смог проверить. В чистом виде логин пароль передавать нельзя по понятным причинам, да и взять его клиентской программе неоткуда.

Насчет первого коннекта - вполне можно, чтобы программа соединилась под каким-то дефолтным прошитым логином паролем, с урезанными правами и возможностью только вызывать хранимку аля "Loging(..)"


 
Необычный Порошок   (2013-10-01 22:21) [16]

ну и как тебе это, вук?

я был безнадежно далек от истины?


 
Anatoly Podgoretsky ©   (2013-10-01 22:26) [17]

> Mikki  (01.10.2013 22:19:15)  [15]

В MS SQL встроенная и не отключается.


 
Необычный Порошок   (2013-10-01 22:30) [18]

Насчет первого коннекта - вполне можно, чтобы программа соединилась под каким-то дефолтным прошитым логином паролем, с урезанными правами и возможностью только вызывать хранимку аля "Loging(..)"


ну вот и все.
и нет у тебя никакой доменной авторизации.
будь хранимка хоть аля, хоть траляля.

PS
TAdoConnection
свойство connectionstring
тыкнув в него откроешь мастер настройки подключения.
и будет тебе там доменная авторизация.


 
DVM ©   (2013-10-01 22:40) [19]


> Необычный Порошок   (01.10.13 22:30) [18]


> TAdoConnection
> свойство connectionstring
> тыкнув в него откроешь мастер настройки подключения.
> и будет тебе там доменная авторизация.

Не будет, если СУБД не поддерживает. Ему же для абстрактной СУБД в вакууме надо.


 
DVM ©   (2013-10-01 22:46) [20]


> Mikki   (01.10.13 22:19) [15]


> хотелось бы понять алгоритм / действия, не опираясь на базу
> MS SQL.
>
> Конечно, хотелось бы опыта человека, который прикручивал
> AD авторизацию в приложении windows на delphi.

Читай про NTLM например


 
Необычный Порошок   (2013-10-01 23:00) [21]

Не будет, если СУБД не поддерживает. Ему же для абстрактной СУБД в вакууме надо.

будет и абстрактная поддерживать.
немного абстрактно, правда, но будет.


 
DVM ©   (2013-10-01 23:04) [22]


> Необычный Порошок   (01.10.13 23:00) [21]


> будет и абстрактная поддерживать.

каким образом? Сервер субд при такой аутентификации должен сам обращаться в каталог, если он это не умеет, то кто?


 
Inovet ©   (2013-10-01 23:18) [23]

> [22] DVM ©   (01.10.13 23:04)

Он будет обращаться

> [21] Необычный Порошок   (01.10.13 23:00)
> немного абстрактно


 
Необычный Порошок   (2013-10-01 23:19) [24]

абстрактным образом. абстракным.
у нас же кроме абстрактного сервера еще и асбстрактная библиотека доступа к серверу.
в вакууме.
но думаю что через пару дней мы и про нее узнаем что-то конкретное тоже.


 
Mikki   (2013-10-02 00:03) [25]


> Читай про NTLM например

это слишком расплывчато. Я могу еще вспомнить слова "читай про kerberos". Так можно читать следующий год весь(

Интересует реальный опыт, кто может такое делал или видел, может дать конкретное направление. Можно вообще рассмотреть даже без БД - например, есть самописный сервер и есть самописный клиент. Как серверу сделать авторизацию клиента по AD?
Подозреваю, что это достаточно сложно, поэтому вопрос, наверное, сводится к тому - есть ли какие-то готовые инструменты / API или их нету. Желательно в контексте прикручиванию к дельфи.


 
DVM ©   (2013-10-02 10:13) [26]


> Mikki   (02.10.13 00:03) [25]


> это слишком расплывчато. Я могу еще вспомнить слова "читай
> про kerberos". Так можно читать следующий год весь(

Читать тебе все равно придется. Нет волшебного компонента "NTLM аутентификация в абстрактной СУБД". Тебе уже неоднократно намекали, что хорошо бы назвать  СУБД, ибо в большинстве мне известных такая аутентификация имеется, чаще всего она фигурирует там под именем Trust Authentication.
Оракл - да, MSSQL - да, Firebird - да, PostgreSQL -да.


 
Anatoly Podgoretsky ©   (2013-10-02 11:13) [27]

Раз не признается, то у него MS SQL


 
Eraser ©   (2013-10-02 17:12) [28]


> Mikki   (02.10.13 00:03) [25]

если безотносительно к СУБД, то такие компоненты в делифи есть, более того, в стандартной поставке.


 
Mikki   (2013-10-02 17:32) [29]

Eraser, ясно, спасибо большое.


 
sniknik ©   (2013-10-02 22:43) [30]

когда винда стартует программу она определяет "токен" (паспорт) процесса... оттуда можно узнать из под какого юзера запущена, в каких группах он состоит (доменные тоже определяются)... возможно еще что-то но этого хватит для "авторизации",  просто прочитай в клиенте и отправь на сервер, а тот пусть в ответ список прав со всех групп/юзера...  
понадобятся функции
OpenThreadToken
OpenProcessToken
GetTokenInformation
ну и наверное
ConvertSidToStringSid
т.к. там все сидами представлено, чтобы было наглядно для админа твоей системы нужно чтобы группы имели привычный вид (а оперировать в программе лучше тоже сид-ами).


 
DVM ©   (2013-10-02 23:01) [31]


> sniknik ©   (02.10.13 22:43) [30]


> возможно еще что-то но этого хватит для "авторизации"

Для авторизации то хватит, но не хватит для аутентификации. Нужна полноценная многоходовая проверка подлинности пользователя контроллером домена на основании присланных пользователем данных. И обращаться к контроллеру должен сервер, к которому хочет получить доступ пользователь.


 
Mikki   (2013-10-02 23:39) [32]

sniknik, спасибо! Но тут DVM правду говорит, в описанном тобой варианте это все будет держаться на честном слове, а по хорошему серверу нужно как-то проверить, что присылаемые данные - верные. Иначе зная API можно подставить доменное имя любого компа (ну или там sid)


 
Дмитрий СС   (2013-10-03 01:24) [33]

Вот заголовочный файл для функций, которые позволяют это делать.


unit uSSPI;

interface

uses Windows, SysUtils;

type
  // Secur32.dll function prototypes
  TQueryPackageInfo = function(PackageName : PChar;
                               var PackageInfo : pointer) : integer; stdcall;

  TFreeContextBuffer = function(pBuffer : pointer) : integer; stdcall;

  TFreeCredentialsHandle = function(var hCred : Int64) : integer; stdcall;

  TDeleteSecurityContext = function(var hCred : Int64) : integer; stdcall;

  TAcquireCredentialsHandle = function(pszPrincipal : PChar;
                                       pszPackage : PChar;
                                       fCredentialUse : DWORD;
                                       pvLogonID : DWORD;
                                       pAuthData : pointer;
                                       pGetKeyFn : DWORD;
                                       pvGetKeyArgument : pointer;
                                       var phCredential : Int64;
                                       var ptsExpiry : TTimeStamp) : integer; stdcall;

  TInitializeSecurityContext = function(var phCredential : Int64;
                                        phContext : pointer;
                                        pszTargetName : PChar;
                                        fContextReq : DWORD;
                                        Reserved1 : DWORD;
                                        TargetDataRep : DWORD;
                                        pInput : pointer;
                                        Reserved2 : DWORD;
                                        var phNewContext : Int64;
                                        pOutput : pointer;
                                        var pfContextAttr : Int64;
                                        var ptsExpiry : TTimeStamp) : integer; stdcall;

  TAcceptSecurityContext = function(var phCredential : Int64;
                                        phContext : pointer;
                                        pInput : pointer;
                                        fContextReq : DWORD;
                                        TargetDataRep : DWORD;
                                        var phNewContext : Int64;
                                        pOutput : pointer;
                                        var pfContextAttr : Int64;
                                        var ptsExpiry : TTimeStamp) : integer; stdcall;
  TQuerySecurityContextToken = function (phContext : pointer; var Token: THandle): Integer; stdcall;

 // AcquireCredentialsHandle() Internal Structure
  PAuthIdentity = ^TAuthIdentity;
  TAuthIdentity = packed record
    User : PChar;
    UserLength : DWORD;
    Domain : PChar;
    DomainLength : DWORD;
    Password : PChar;
    PasswordLength : DWORD;
    Flags : DWORD;
  end;

  // QuerySecurityPackageInfo Internal Structure
  PSecPkgInfo = ^TSecPkgInfo;
  TSecPkgInfo = packed record
    Capabilities : DWORD;
    Version : WORD;
    RPCID : WORD;
    MaxToken : DWORD;
    Name : PChar;
    Comment : PChar;
  end;

  // InitializeSecurityContext() Internal structure
  PSecBuffer = ^TSecBuffer;
  TSecBuffer = packed record
    cbBuffer : DWORD;
    BufferType : DWORD;
    pvBuffer : pointer;
  end;

  PSecBuffDesc = ^TSecBuffDesc;
  TSecBuffDesc = packed record
    ulVersion : DWORD;
    cBuffers : DWORD;
    pBuffers : PSecBuffer;
  end;

var
 FQueryPackageInfo : TQueryPackageInfo;
 FFreeContextBuffer : TFreeContextBuffer;
 FAcquireCredHandle : TAcquireCredentialsHandle;
 FFreeCredHandle : TFreeCredentialsHandle;
 FInitSecContext : TInitializeSecurityContext;
 FDelSecContext : TDeleteSecurityContext;
 FAcceptSecContext : TAcceptSecurityContext;
 QuerySecurityContextToken: TQuerySecurityContextToken;
 FSecHandle : THandle;

implementation

initialization
 FSecHandle := LoadLibrary("SECUR32.DLL");
 FQueryPackageInfo := nil;
 FFreeContextBuffer := nil;
 FAcquireCredHandle := nil;
 FFreeCredHandle := nil;
 FInitSecContext := nil;
 FDelSecContext := nil;
 FAcceptSecContext := nil;

 if FSecHandle <> 0 then begin
   @FQueryPackageInfo := GetProcAddress(FSecHandle,"QuerySecurityPackageInfoW");
   @FFreeContextBuffer := GetProcAddress(FSecHandle,"FreeContextBuffer");
   @FAcquireCredHandle := GetProcAddress(FSecHandle,"AcquireCredentialsHandleW");
   @FInitSecContext := GetProcAddress(FSecHandle,"InitializeSecurityContextW");
   @FFreeCredHandle := GetProcAddress(FSecHandle,"FreeCredentialsHandle");
   @FDelSecContext := GetProcAddress(FSecHandle,"DeleteSecurityContext");
   @FAcceptSecContext := GetProcAddress(FSecHandle,"AcceptSecurityContext");
   @QuerySecurityContextToken := GetProcAddress(FSecHandle,"QuerySecurityContextToken");
 end else begin
   raise Exception.Create("Невозможно загрузить SECUR32.DLL.");
 end;

finalization
 if FSecHandle <> 0 then
   FreeLibrary(FSecHandle);
end.



 
Дмитрий СС   (2013-10-03 01:29) [34]

Вот тут можно наковырять пример клиента http://yadi.sk/d/C890RkuBAJR99
Сервер, к сожалению, не сохранился =(


 
Eraser ©   (2013-10-03 03:32) [35]

Вот код со стороны сервера, в на стороне клиента почти тоже самое.

function XXX.ServerHandshakeAuth(AContext: TIdContext;
 ASessionKey: THandle; var AllowedRights: DWORD): Boolean;
var
 hContext: CtxtHandle;
 phTempContext: PCtxtHandle;
 tsExpires: TimeStamp;
 pszPackage: PSecWChar;
 ss, CrendHandleStat: SECURITY_STATUS;
 bFirstPass: Boolean;
 pbTokenBuf: System.PByte;
 lSize, lAttributes: ULONG;
 msAuth: TMemoryStream;

 // Declare in and out buffers.
 secBufferOut, secBufferIn: array [0..0] of SecBuffer;
 secBufDescriptorOut, secBufDescriptorIn: SecBufferDesc;
 hCredentials: CredHandle;
 pSD: JwaWinNT.PSECURITY_DESCRIPTOR;
 hToken: THandle;
begin
 Result := False;

 bFirstPass := True;
 hToken := 0;
 AllowedRights := 0;
 FillChar(hCredentials, SizeOf(hCredentials), 0);
 ss := SEC_I_CONTINUE_NEEDED;
 lAttributes := ISC_REQ_STREAM;

 msAuth := TMemoryStream.Create;
 try
   pszPackage := PSecWChar(PChar(NTLMSP_NAME));

   EnterCriticalSection(csCrypto);
   try
     CrendHandleStat := AcquireCredentialsHandle(nil,
       pszPackage,
       SECPKG_CRED_BOTH,
       nil, nil, nil, nil,
       @hCredentials, tsExpires);
     if CrendHandleStat <> SEC_E_OK then
     begin
       Exit;
     end;
   finally
     LeaveCriticalSection(csCrypto);
   end;

   while ss = SEC_I_CONTINUE_NEEDED do
   begin
     msAuth.Clear;
     lSize := AContext.Connection.IOHandler.ReadLongInt;
     AContext.Connection.IOHandler.ReadStream(msAuth, lSize);
     msAuth.Position := 0;

     //DecryptData(ASessionKey, msAuth);

     GetMem(pbTokenBuf, lSize);
     msAuth.Read(pbTokenBuf^, msAuth.Size);

     // Point "In Buffer" to blob.
     secBufferIn[0].BufferType := SECBUFFER_TOKEN;
     secBufferIn[0].cbBuffer := lSize;
     secBufferIn[0].pvBuffer := pbTokenBuf;

     // Point "In" BufDesc to in buffer.
     secBufDescriptorIn.cBuffers := 1;
     secBufDescriptorIn.pBuffers := @secBufferIn;
     secBufDescriptorIn.ulVersion := SECBUFFER_VERSION;

      // Set up out buffer,
      // (The SSPI will be allocating buffers for us).
     secBufferOut[0].BufferType := SECBUFFER_TOKEN;
     secBufferOut[0].cbBuffer := 0;
     secBufferOut[0].pvBuffer := nil;

     // Point "Out" Bufdesc to out buffer.
     secBufDescriptorOut.cBuffers := 1;
     secBufDescriptorOut.pBuffers := @secBufferOut;
     secBufDescriptorOut.ulVersion := SECBUFFER_VERSION;

     if bFirstPass then
       phTempContext := nil
     else
       phTempContext := @hContext;

     EnterCriticalSection(csCrypto);
     try
       ss := AcceptSecurityContext(@hCredentials,
         phTempContext,
         @secBufDescriptorIn,
         lAttributes or ISC_REQ_ALLOCATE_MEMORY,
         SECURITY_NETWORK_DREP,
         @hContext,
         @secBufDescriptorOut,
         lAttributes,
         nil);
     finally
       LeaveCriticalSection(csCrypto);
     end;

     // No longer first pass through the loop
     bFirstPass := False;

     // Was a blob output? If so, send it.
     if secBufferOut[0].cbBuffer <> 0 then
     begin
       // Client communication!
       msAuth.Clear;
       msAuth.Write(secBufferOut[0].pvBuffer^, secBufferOut[0].cbBuffer);

       msAuth.Position := 0;

       AContext.Connection.IOHandler.Write(Integer(msAuth.Size));
       AContext.Connection.IOHandler.Write(msAuth, msAuth.Size);

       EnterCriticalSection(csCrypto);
       try
         // Free out buffer.
         FreeContextBuffer(secBufferOut[0].pvBuffer);
       finally
         LeaveCriticalSection(csCrypto);
       end;
     end;

     if pbTokenBuf <> nil then
       FreeMem(pbTokenBuf);
   end;

   if ss <> SEC_E_OK then
     Exit;

   pSD := GetSD;
   if pSD = nil then
   begin
     Exit;
   end;

   EnterCriticalSection(csCrypto);
   try
     ss := QuerySecurityContextToken(@hContext, @hToken);
     if ss = SEC_E_OK then
     begin
       try
// Тут проверка результата. см. ф-ю AccessCheck
         Result := ZZZZZZZZZ(hToken, pSD, AllowedRights);
       finally
         if hToken <> 0 then
           CloseHandle(hToken);
       end;
     end;
   finally
     LeaveCriticalSection(csCrypto);
   end;
 finally
   EnterCriticalSection(csCrypto);
   try
     DeleteSecurityContext(@hContext);
     FreeCredentialsHandle(@hCredentials);
   finally
     LeaveCriticalSection(csCrypto);
   end;

   msAuth.Free;
 end;
end;


 
sniknik ©   (2013-10-03 08:23) [36]

> нужно как-то проверить, что присылаемые данные - верные.
> доменное имя любого компа (ну или там sid)
не компа, а юзера/его групп, на сервере проверка будет почти "автоматом", тебе же там придется делать соответствие сид -> имя при чтении/задании прав (админы прописывают у имен, ты работаешь/присылаешь сиды), а это получится ТОЛЬКО если программа/юзер в домене, сиды уникальны (есть еще предопределенные, локальные, стандартные везде, но не доменные), т.е. даже одноименные например юзер сделан локально и в домене... не найдет локального.

в общем не все так просто для "честного слова" даже зная АПИ (клиент твой? или другие пишут, зачем АПИ в смысле). вот если "в разрыв" где нибудь через прокси пустить, там чужое подставить можно, да... но во первых, тебе насколько серьезную защиту нужно? будут "ломать" или "от дурака"? а то нужна калитка в сад, а пытаются сейфовую дверь банка спроектировать...
ну и во вторых можно же пустить авторизацию по шифрованному каналу.

p.s.  но вообще ладно, не подходит так не подходит.


 
sniknik ©   (2013-10-03 10:36) [37]

+
> Для авторизации то хватит, но не хватит для аутентификации.
для аутентификации вроде вот это подойдет
http://www.delphikingdom.com/asp/viewitem.asp?catalogid=1341
помнил что есть статья, когда свое делал натыкался, но пока нашел... мне по какой то причине, не помню, не подошло. (вроде из-за того что тут аутентификация на сервере по данным с клиента, а мне нужно было идентифицировать именно клиента, на локальной машине... права в программе давались именно на него, просто хранятся на сервере)


 
DVM ©   (2013-10-03 10:45) [38]


> sniknik ©   (03.10.13 10:36) [37]


> помнил что есть статья, когда свое делал натыкался, но пока
> нашел...

вот и я про нее подумал сразу, помню Набережных автор, а где видел как называется забыл совсем.


 
Петрович   (2014-02-17 23:40) [39]

Возможно я не совсем понял что хочет автор темы, но если о

> а по хорошему серверу нужно как-то проверить, что присылаемые
> данные - верные.

то например в MSSQL есть системная функция SYSTEM_USER возвращающая имя пользователя "подлогиненного" к серверу. Причем, если использовалась Windows-авторизация то SYSTEM_USER вернет DOMAIN\user_login_name а если серверная, то имя серверного юзверя.
Вот тебе и карты в руки - хочешь сравнивай что тебе прислали, хочешь вообще опирайся на SYSTEM_USER и не смотри на то что тебе присылает хитрый враг прикидывающийся юзверем.
Правда, здесь есть одно НО. Опираться на DOMAIN\user_login_name вообще-то не хорошо. Поскольку "DOMAIN\Vasya" сегодня, и "DOMAIN\Vasya" завтра, это не всегда один и тот же юзверь. Правильнее работать с SID пользователя.
Например можно взгянуть сюда http://habrahabr.ru/post/111978/



Страницы: 1 вся ветка

Форум: "Прочее";
Текущий архив: 2014.09.28;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.59 MB
Время: 0.003 c
15-1393055181
vajo
2014-02-22 11:46
2014.09.28
Выбор железа для PC


15-1392755402
Юрий
2014-02-19 00:30
2014.09.28
С днем рождения ! 19 февраля 2014 среда


3-1300677386
Дмитрий С
2011-03-21 06:16
2014.09.28
Как быстрее с подзапросом или с присоединением сгруппированной..?


2-1382366498
Ринат
2013-10-21 18:41
2014.09.28
Создание программы расчета


1-1328171624
DDDfs
2012-02-02 12:33
2014.09.28
Обратиться к Child форме если она создана в процессе работы прогр





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский