Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Comment: | {1048} introducing TSQLRestServerAuthenticationActiveDirectory class, thanks to an implementation proposal from EgorovAlex - thanks for sharing! |
---|---|
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
f1e71989545b57052b8dea70997ec879 |
User & Date: | ab 2015-03-06 14:45:11 |
2015-03-08
| ||
19:26 | {1049} fixed modNoMongo format support when returning a MongoDB query as JSON check-in: ac92b58ad4 user: ab tags: trunk | |
2015-03-06
| ||
14:45 | {1048} introducing TSQLRestServerAuthenticationActiveDirectory class, thanks to an implementation proposal from EgorovAlex - thanks for sharing! check-in: f1e7198954 user: ab tags: trunk | |
14:44 | {1047} added class function TAESAbstract.SimpleEncrypt() check-in: 122f1daec8 user: ab tags: trunk | |
Changes to SQLite3/mORMot.pas.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 ... 846 847 848 849 850 851 852 853 854 855 856 857 858 859 ..... 12077 12078 12079 12080 12081 12082 12083 12084 12085 12086 12087 12088 12089 12090 12091 ..... 12344 12345 12346 12347 12348 12349 12350 12351 12352 12353 12354 12355 12356 12357 12358 12359 12360 12361 12362 12363 12364 ..... 12371 12372 12373 12374 12375 12376 12377 12378 12379 12380 12381 12382 12383 12384 12385 12386 12387 12388 12389 12390 12391 ..... 40252 40253 40254 40255 40256 40257 40258 40259 40260 40261 40262 40263 40264 40265 ..... 40296 40297 40298 40299 40300 40301 40302 40303 40304 40305 40306 40307 40308 40309 ..... 40718 40719 40720 40721 40722 40723 40724 40725 40726 40727 40728 40729 40730 40731 40732 40733 40734 40735 40736 40737 40738 40739 40740 ..... 40785 40786 40787 40788 40789 40790 40791 40792 40793 40794 40795 40796 40797 40798 |
Portions created by the Initial Developer are Copyright (C) 2015 the Initial Developer. All Rights Reserved. Contributor(s): Alexander (chaa) Alfred Glaenzer (alf) DigDiver Esmond Pavel (mpv) Jordi Tudela Martin Suer MilesYou Sabbiolina Vadim Orel ................................................................................ InitializeTable methods, to tune underlying table creation (e.g. indexes) - introducing TInterfaceStub and TInterfaceMock classes to define high-performance interface stubbing and mocking via a fluent interface - integrated Windows Authentication to the mORMot Client-Server layer: in order to enable it, define a SSPIAUTH conditional and call TSQLRestClientURI.SetUser() with an empty user name, and ensure that TSQLAuthUser.LoginName contains a matching 'DomainName\UserName' value - added TSQLRecordTimed class, and TSQLRecord.AddFilterNotVoidAllTextFields and TSQLModel.AddTableInherited methods - Windows Authentication can use either NTLM or the more secure Kerberos protocol, if the corresponding SPN domain is set as password - feature request [5a17a4277f]: you can now define in the Model your custom TSQLAuthUser and/or TSQLAuthGroup classes to store the authorization information: TSQLRestServer will search for any table inheriting from ................................................................................ TSQLRestServerAuthenticationClientSetUserPassword = ( passClear, passHashed, passKerberosSPN); /// optional behavior of TSQLRestServerAuthentication class // - by default, saoUserByLogonOrID is set, allowing // TSQLRestServerAuthentication.GetUser() to retrieve the TSQLAuthUser by // logon name or by ID, if the supplied logon name is an integer TSQLRestServerAuthenticationOption = (saoUserByLogonOrID); /// defines the optional behavior of TSQLRestServerAuthentication class TSQLRestServerAuthenticationOptions = set of TSQLRestServerAuthenticationOption; /// abstract class used to implement server-side authentication in TSQLRestServer // - inherit from this class to implement expected authentication scheme TSQLRestServerAuthentication = class ................................................................................ /// handle the Auth RESTful method with HTTP Basic // - will first return HTML_UNAUTHORIZED (401), then expect user and password // to be supplied as incoming "Authorization: Basic ...." headers function Auth(Ctxt: TSQLRestServerURIContext): boolean; override; end; {$ifdef SSPIAUTH} /// authentication using Windows Security Support Provider Interface (SSPI) // - is able to authenticate using either NTLM or Kerberos // - ClientSetUser() will ignore aUserName, and expect aPassword to be either // '' if you expect NTLM authentication to take place, or contain the SPN // registration (e.g. 'mymormotservice/myserver.mydomain.tld') for Kerberos // authentication TSQLRestServerAuthenticationSSPI = class(TSQLRestServerAuthenticationSignedURI) protected /// Windows built-in authentication // - holds information between calls to ServerSSPIAuth fSSPIAuthContexts: TSecContextDynArray; /// class method used on client side to create a remote session // - will call the ModelRoot/Auth service, i.e. call TSQLRestServer.Auth() // published method to create a session for this user: so ................................................................................ class function ClientComputeSessionKey(Sender: TSQLRestClientURI; User: TSQLAuthUser): RawUTF8; override; public /// initialize the authentication method to a specified server constructor Create(aServer: TSQLRestServer); override; /// finalize internal memory structures destructor Destroy; override; /// will try to handle the Auth RESTful method with Windows SSPI API // - to be called in a two pass "challenging" algorithm, as implemented by // TSQLRestServerAuthenticationSignedURI.Auth method // - the client-side logged user will be identified as valid, according // to a Windows SSPI API secure challenge function Auth(Ctxt: TSQLRestServerURIContext): boolean; override; end; {$endif} /// TSynAuthentication* class using TSQLAuthUser/TSQLAuthGroup for credentials // - could be used e.g. for SynDBRemote access in conjunction with mORMot TSynAuthenticationRest = class(TSynAuthenticationAbstract) protected fServer: TSQLRestServer; fAllowedGroups: TIntegerDynArray; ................................................................................ err: integer; begin UserID := GetInt64(pointer(aUserName),err); if (err=0) and (UserID>0) and (saoUserByLogonOrID in fOptions) then // TSQLAuthUser.ID was transmitted -> may use the ORM cache result := fServer.fSQLAuthUserClass.Create(fServer,UserID) else result := fServer.fSQLAuthUserClass.Create(fServer,'LogonName=?',[aUserName]); if result.fID=0 then begin {$ifdef WITHLOG} fServer.fLogFamily.SynLog.Log(sllUserAuth, '%.LogonName=% not found in table',[result.ClassType,aUserName],self); {$endif} FreeAndNil(result); end else ................................................................................ if Sender=nil then exit; try Sender.SessionClose; U := TSQLAuthUser.Create; try U.LogonName := trim(aUserName); if aPassworKind<>passClear then U.PasswordHashHexa := aPassword else U.PasswordPlain := aPassword; // compute SHA256('salt'+aPassword); result := Sender.SessionCreate(self,U,ClientComputeSessionKey(Sender,U)); finally U.Free; end; ................................................................................ if UserName='' then exit; User := GetUser(Ctxt,UserName); if User<>nil then try User.PasswordHashHexa := ''; // override with context fServer.SessionCreate(User,Ctxt,Session); if Session<>nil then begin if BrowserAuth then Ctxt.Returns(JSONEncode(['result',Session.fPrivateSalt, 'logonname',Session.User.LogonName]), HTML_SUCCESS, (SECPKGNAMEHTTPWWWAUTHENTICATE+' ')+BinToBase64(OutData)) else Ctxt.Returns(['result',BinToBase64(SecEncrypt(fSSPIAuthContexts[SecCtxIdx],Session.fPrivateSalt)), 'logonname',Session.User.LogonName,'data',BinToBase64(OutData)]); end; finally User.Free; end; finally FreeSecContext(fSSPIAuthContexts[SecCtxIdx]); CtxArr.Delete(SecCtxIdx); end; ................................................................................ destructor TSQLRestServerAuthenticationSSPI.Destroy; var i: integer; begin for i := 0 to High(fSSPIAuthContexts) do FreeSecContext(fSSPIAuthContexts[i]); inherited; end; {$endif SSPIAUTH} { TSynAuthenticationRest } constructor TSynAuthenticationRest.Create(aServer: TSQLRestServer; const aAllowedGroups: array of integer); |
> > > > > > > | > > > | > | > | | < > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > | | | | | < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 ... 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 ..... 12080 12081 12082 12083 12084 12085 12086 12087 12088 12089 12090 12091 12092 12093 12094 12095 12096 12097 12098 12099 ..... 12352 12353 12354 12355 12356 12357 12358 12359 12360 12361 12362 12363 12364 12365 12366 12367 12368 12369 12370 12371 12372 12373 12374 12375 12376 ..... 12383 12384 12385 12386 12387 12388 12389 12390 12391 12392 12393 12394 12395 12396 12397 12398 12399 12400 12401 12402 12403 12404 12405 12406 12407 12408 12409 12410 12411 12412 12413 12414 12415 12416 12417 12418 12419 12420 12421 12422 12423 12424 12425 12426 12427 12428 12429 12430 ..... 40291 40292 40293 40294 40295 40296 40297 40298 40299 40300 40301 40302 40303 40304 40305 40306 40307 40308 40309 40310 ..... 40341 40342 40343 40344 40345 40346 40347 40348 40349 40350 40351 40352 40353 40354 40355 ..... 40764 40765 40766 40767 40768 40769 40770 40771 40772 40773 40774 40775 40776 40777 40778 40779 40780 40781 40782 40783 40784 40785 ..... 40830 40831 40832 40833 40834 40835 40836 40837 40838 40839 40840 40841 40842 40843 40844 40845 40846 40847 40848 40849 40850 40851 40852 40853 40854 40855 40856 40857 40858 40859 40860 40861 40862 40863 40864 40865 40866 40867 40868 40869 40870 40871 40872 40873 40874 40875 40876 40877 40878 40879 40880 40881 40882 40883 40884 40885 40886 40887 40888 40889 40890 40891 40892 40893 40894 40895 40896 40897 40898 40899 40900 40901 40902 40903 40904 40905 40906 40907 40908 40909 40910 40911 40912 40913 40914 40915 40916 40917 |
Portions created by the Initial Developer are Copyright (C) 2015 the Initial Developer. All Rights Reserved. Contributor(s): Alexander (chaa) Alfred Glaenzer (alf) DigDiver EgorovAlex Esmond Pavel (mpv) Jordi Tudela Martin Suer MilesYou Sabbiolina Vadim Orel ................................................................................ InitializeTable methods, to tune underlying table creation (e.g. indexes) - introducing TInterfaceStub and TInterfaceMock classes to define high-performance interface stubbing and mocking via a fluent interface - integrated Windows Authentication to the mORMot Client-Server layer: in order to enable it, define a SSPIAUTH conditional and call TSQLRestClientURI.SetUser() with an empty user name, and ensure that TSQLAuthUser.LoginName contains a matching 'DomainName\UserName' value - introducing TSQLRestServerAuthenticationActiveDirectory class, thanks to an implementation proposal from EgorovAlex - thanks for sharing! - added TSQLRecordTimed class, and TSQLRecord.AddFilterNotVoidAllTextFields and TSQLModel.AddTableInherited methods - Windows Authentication can use either NTLM or the more secure Kerberos protocol, if the corresponding SPN domain is set as password - feature request [5a17a4277f]: you can now define in the Model your custom TSQLAuthUser and/or TSQLAuthGroup classes to store the authorization information: TSQLRestServer will search for any table inheriting from ................................................................................ TSQLRestServerAuthenticationClientSetUserPassword = ( passClear, passHashed, passKerberosSPN); /// optional behavior of TSQLRestServerAuthentication class // - by default, saoUserByLogonOrID is set, allowing // TSQLRestServerAuthentication.GetUser() to retrieve the TSQLAuthUser by // logon name or by ID, if the supplied logon name is an integer // - if saoHandleUnknownLogonAsStar is defined, any user successfully // authenticated could be logged with the same ID (and authorization) // than TSQLAuthUser.Logon='*' - of course, this is meaningfull only with // an external credential check (e.g. via SSPI or Active Directory) TSQLRestServerAuthenticationOption = ( saoUserByLogonOrID, saoHandleUnknownLogonAsStar); /// defines the optional behavior of TSQLRestServerAuthentication class TSQLRestServerAuthenticationOptions = set of TSQLRestServerAuthenticationOption; /// abstract class used to implement server-side authentication in TSQLRestServer // - inherit from this class to implement expected authentication scheme TSQLRestServerAuthentication = class ................................................................................ /// handle the Auth RESTful method with HTTP Basic // - will first return HTML_UNAUTHORIZED (401), then expect user and password // to be supplied as incoming "Authorization: Basic ...." headers function Auth(Ctxt: TSQLRestServerURIContext): boolean; override; end; {$ifdef SSPIAUTH} /// authentication of the current logged user using Windows Security Support // Provider Interface (SSPI) // - is able to authenticate the currently logged user on the client side, // using either NTLM or Kerberos - it would allow to safely authenticate // on a mORMot server without prompting the user to enter its password // - ClientSetUser() will ignore aUserName, and expect aPassword to be either // '' if you expect NTLM authentication to take place, or contain the SPN // registration (e.g. 'mymormotservice/myserver.mydomain.tld') for Kerberos // authentication TSQLRestServerAuthenticationSSPI = class(TSQLRestServerAuthenticationURI) protected /// Windows built-in authentication // - holds information between calls to ServerSSPIAuth fSSPIAuthContexts: TSecContextDynArray; /// class method used on client side to create a remote session // - will call the ModelRoot/Auth service, i.e. call TSQLRestServer.Auth() // published method to create a session for this user: so ................................................................................ class function ClientComputeSessionKey(Sender: TSQLRestClientURI; User: TSQLAuthUser): RawUTF8; override; public /// initialize the authentication method to a specified server constructor Create(aServer: TSQLRestServer); override; /// finalize internal memory structures destructor Destroy; override; /// will try to handle the Auth RESTful method with Windows SSPI API // - to be called in a two pass algorithm, used to cypher the password // - the client-side logged user will be identified as valid, according // to a Windows SSPI API secure challenge function Auth(Ctxt: TSQLRestServerURIContext): boolean; override; end; /// authentication of a User and its Password using Active Directory // - ClientSetUser() will use aUserName as 'Domain\Login', and will validate // aPassword against the one registered for his/her account TSQLRestServerAuthenticationActiveDirectory = class(TSQLRestServerAuthenticationURI) protected class function ClientComputeSessionKey(Sender: TSQLRestClientURI; User: TSQLAuthUser): RawUTF8; override; public /// will try to handle the Auth RESTful method with Windows SSPI API // - to be called in a two pass "challenging" algorithm, as implemented by // TSQLRestServerAuthenticationSignedURI.Auth method // - the user name and password supplied from the client side will be // checked against the domain supplied in the user name (e.g. 'Domain\Login') function Auth(Ctxt: TSQLRestServerURIContext): boolean; override; /// class method to be used on client side to create a remote session // - call TSQLRestServerAuthenticationActiveDirectory.ClientSetUser() instead // of TSQLRestClientURI.SetUser(), and never the method of this abstract class // - expects aUserName to be in form of 'Domain\Login', so that 'Login' // will be checked via Active Direction with the supplied password against // the supplied 'Domain' - if 'Domain' is the computer name where the server // is started (i.e. 'ComputeName\Login'), the user will be searched within // the registered local accounts login // - needs the plain aPassword, so aPasswordKind should be passClear // - returns true on success class function ClientSetUser(Sender: TSQLRestClientURI; const aUserName, aPassword: RawUTF8; aPassworKind: TSQLRestServerAuthenticationClientSetUserPassword=passClear): boolean; override; end; {$endif SSPIAUTH} /// TSynAuthentication* class using TSQLAuthUser/TSQLAuthGroup for credentials // - could be used e.g. for SynDBRemote access in conjunction with mORMot TSynAuthenticationRest = class(TSynAuthenticationAbstract) protected fServer: TSQLRestServer; fAllowedGroups: TIntegerDynArray; ................................................................................ err: integer; begin UserID := GetInt64(pointer(aUserName),err); if (err=0) and (UserID>0) and (saoUserByLogonOrID in fOptions) then // TSQLAuthUser.ID was transmitted -> may use the ORM cache result := fServer.fSQLAuthUserClass.Create(fServer,UserID) else result := fServer.fSQLAuthUserClass.Create(fServer,'LogonName=?',[aUserName]); if (result.fID=0) and (saoHandleUnknownLogonAsStar in fOptions) then if fServer.Retrieve('LogonName=?',[],['*'],result) then begin result.LogonName := aUserName; result.DisplayName := aUserName; end; if result.fID=0 then begin {$ifdef WITHLOG} fServer.fLogFamily.SynLog.Log(sllUserAuth, '%.LogonName=% not found in table',[result.ClassType,aUserName],self); {$endif} FreeAndNil(result); end else ................................................................................ if Sender=nil then exit; try Sender.SessionClose; U := TSQLAuthUser.Create; try U.LogonName := trim(aUserName); U.DisplayName := U.LogonName; if aPassworKind<>passClear then U.PasswordHashHexa := aPassword else U.PasswordPlain := aPassword; // compute SHA256('salt'+aPassword); result := Sender.SessionCreate(self,U,ClientComputeSessionKey(Sender,U)); finally U.Free; end; ................................................................................ if UserName='' then exit; User := GetUser(Ctxt,UserName); if User<>nil then try User.PasswordHashHexa := ''; // override with context fServer.SessionCreate(User,Ctxt,Session); if Session<>nil then if BrowserAuth then Ctxt.Returns(JSONEncode(['result',Session.fPrivateSalt, 'logonname',Session.User.LogonName]),HTML_SUCCESS, (SECPKGNAMEHTTPWWWAUTHENTICATE+' ')+BinToBase64(OutData)) else Ctxt.Returns([ 'result',BinToBase64(SecEncrypt(fSSPIAuthContexts[SecCtxIdx],Session.fPrivateSalt)), 'logonname',Session.User.LogonName,'data',BinToBase64(OutData)]); finally User.Free; end; finally FreeSecContext(fSSPIAuthContexts[SecCtxIdx]); CtxArr.Delete(SecCtxIdx); end; ................................................................................ destructor TSQLRestServerAuthenticationSSPI.Destroy; var i: integer; begin for i := 0 to High(fSSPIAuthContexts) do FreeSecContext(fSSPIAuthContexts[i]); inherited; end; { TSQLRestServerAuthenticationActiveDirectory } const AD_SALT = '5aLt'; function TSQLRestServerAuthenticationActiveDirectory.Auth(Ctxt: TSQLRestServerURIContext): boolean; function CheckPassword(const UserName,Password: RawUTF8): Boolean; var Domain,Login: RawUTF8; hToken: THandle; begin split(UserName,Domain,Login); result := LogonUser(pointer(UTF8ToString(Login)),pointer(UTF8ToString(Domain)), pointer(UTF8ToString(Password)),9,LOGON32_PROVIDER_WINNT50,hToken); if result then CloseHandle(hToken); end; var aUserName, aHashedPassWord, aPassword: RawUTF8; User: TSQLAuthUser; begin result := true; if AuthSessionRelease(Ctxt) then exit; aUserName := Ctxt.InputUTF8OrVoid['UserName']; if aUserName<>'' then begin User := GetUser(Ctxt,aUserName); if User<>nil then try User.PasswordHashHexa := ''; // not needed, especially if from LogonName='*' aHashedPassWord := Base64ToBin(Ctxt.InputUTF8OrVoid['Password']); aPassword := TAESCFB.SimpleEncrypt(aHashedPassWord,Nonce(false)+AD_SALT,false); if not CheckPassword(aUserName,aPassWord) then begin aPassword := TAESCFB.SimpleEncrypt(aHashedPassWord,Nonce(true)+AD_SALT,false); if not CheckPassword(aUserName,aPassWord) then exit; // current and previous server nonce did not success end; // now client is authenticated -> create a session SessionCreate(Ctxt,User); finally User.Free; end; end else if aUserName<>'' then // only UserName=... -> return hexadecimal nonce content valid for 5 minutes Ctxt.Results([Nonce(false)]) else // parameters does not match any expected layout result := false; end; class function TSQLRestServerAuthenticationActiveDirectory.ClientComputeSessionKey(Sender: TSQLRestClientURI; User: TSQLAuthUser): RawUTF8; var aServerNonce: RawUTF8; begin result := ''; if User.LogonName='' then exit; aServerNonce := Sender.CallBackGetResult('Auth',['UserName',User.LogonName]); if aServerNonce='' then exit; result := Sender.CallBackGetResult('Auth',['UserName',User.LogonName, 'Password',BinToBase64(TAESCFB.SimpleEncrypt( User.PasswordHashHexa,aServerNonce+AD_SALT,true))]); end; class function TSQLRestServerAuthenticationActiveDirectory.ClientSetUser( Sender: TSQLRestClientURI; const aUserName, aPassword: RawUTF8; aPassworKind: TSQLRestServerAuthenticationClientSetUserPassword=passClear): boolean; begin if aPassworKind<>passClear then raise ESecurityException.CreateUTF8('%.ClientSetUser() expects passClear',[self]); result := inherited ClientSetUser(Sender,aUserName,aPassword,passHashed); end; {$endif SSPIAUTH} { TSynAuthenticationRest } constructor TSynAuthenticationRest.Create(aServer: TSQLRestServer; const aAllowedGroups: array of integer); |
Changes to SynopseCommit.inc.
1 |
'1.18.1047'
|
| |
1 |
'1.18.1048'
|