Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Comment: | {1259} BREAKING CHANGE: renamed TSQLRestServer.OnSessionFailed event as TSQLRestServer.OnAuthenticationFailed, including a new TNotifyAuthenticationFailedReason parameter so that the callback would be able to identify which kind of failure did occur |
---|---|
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
59a04453f15231c405aedf1c5e22d4e0 |
User & Date: | ab 2015-04-27 08:34:51 |
2015-04-27
| ||
09:39 | {1260} fixed regression tests to handle [58a19f830d] commit ('"*" or a set of ' comment in ObjectToJSON for enumerates or sets) check-in: 3000457992 user: ab tags: trunk | |
08:34 | {1259} BREAKING CHANGE: renamed TSQLRestServer.OnSessionFailed event as TSQLRestServer.OnAuthenticationFailed, including a new TNotifyAuthenticationFailedReason parameter so that the callback would be able to identify which kind of failure did occur check-in: 59a04453f1 user: ab tags: trunk | |
2015-04-26
| ||
19:13 | {1258} added the ability to create a REST API documentation in AsciiDoc format check-in: 58a19f830d user: ab tags: trunk | |
Changes to SQLite3/mORMot.pas.
867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 .... 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 .... 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 ..... 12334 12335 12336 12337 12338 12339 12340 12341 12342 12343 12344 12345 12346 12347 ..... 12620 12621 12622 12623 12624 12625 12626 12627 12628 12629 12630 12631 12632 12633 ..... 13386 13387 13388 13389 13390 13391 13392 13393 13394 13395 13396 13397 13398 13399 ..... 13467 13468 13469 13470 13471 13472 13473 13474 13475 13476 13477 13478 13479 13480 13481 ..... 32510 32511 32512 32513 32514 32515 32516 32517 32518 32519 32520 32521 32522 32523 32524 32525 32526 32527 32528 32529 32530 32531 ..... 33872 33873 33874 33875 33876 33877 33878 33879 33880 33881 33882 33883 33884 33885 33886 33887 33888 33889 ..... 34123 34124 34125 34126 34127 34128 34129 34130 34131 34132 34133 34134 34135 34136 34137 34138 34139 34140 34141 34142 34143 34144 34145 34146 34147 34148 34149 34150 34151 34152 34153 34154 ..... 41936 41937 41938 41939 41940 41941 41942 41943 41944 41945 41946 41947 41948 41949 41950 ..... 42301 42302 42303 42304 42305 42306 42307 42308 42309 42310 42311 42312 42313 42314 42315 42316 42317 ..... 42441 42442 42443 42444 42445 42446 42447 42448 42449 42450 42451 42452 42453 42454 42455 42456 42457 42458 42459 42460 42461 42462 42463 ..... 42493 42494 42495 42496 42497 42498 42499 42500 42501 42502 42503 42504 42505 42506 42507 42508 42509 42510 42511 42512 42513 42514 ..... 42641 42642 42643 42644 42645 42646 42647 42648 42649 42650 42651 42652 42653 42654 42655 42656 42657 42658 42659 42660 42661 42662 42663 42664 42665 42666 ..... 42751 42752 42753 42754 42755 42756 42757 42758 42759 42760 42761 42762 42763 42764 42765 42766 42767 42768 42769 42770 42771 42772 42773 42774 42775 42776 42777 42778 42779 |
TSQLAuthUser and/or TSQLAuthGroup classes to store the authorization information: TSQLRestServer will search for any table inheriting from TSQLAuthUser/TSQLAuthGroup in the TSQLModel - see also corresponding TSQLRestServer.SQLAuthUserClass/SQLAuthGroupClass new properties - introducing TSQLAuthUser.CanUserLog() to ensure authentication is allowed, as requested by feature request [842906425928] - added TSynAuthenticationRest e.g. for SynDBRemote to check REST users - added TSQLRestServer.OnSessionCreate / OnSessionClosed / OnSessionFailed callbacks, and TSQLRestServerURIContext.AuthenticationFailed virtual method - added TSQLRestServer.SessionClass property to specify the class type to handle in-memory sessions, and override e.g. IsValidURI() method - CreateMissingTables() method is not declared as virtual in TSQLRestServer - TSQLRestServer.URI() and TSQLRestClientURI.InternalURI() methods now uses one TSQLRestURIParams parameter for all request input and output values - TSQLRestServer.URI() method will return "405 Method Not Allowed" error ................................................................................ // ! context := @ServiceContext; // threadvar access once // ! ... PServiceRunningContext = ^TServiceRunningContext; TSQLRestServerURIContext = class; TAuthSession = class; /// will identify the currently running service on the server side // - is the type of the global ServiceContext threadvar // - to access the current TSQLRestServer instance (and e.g. its ORM/CRUD // or SOA methods), use Request.Server and not Factory.Server, which may not // be available e.g. if you run the service from the server side (so no // factory is involved) TServiceRunningContext = record ................................................................................ // - should set Service member (and possibly ServiceMethodIndex) // - abstract implementation which is to be overridden procedure URIDecodeSOAByInterface; virtual; abstract; /// process authentication // - return FALSE in case of invalid signature, TRUE if authenticated function Authenticate: boolean; virtual; /// method called in case of authentication failure // - this default implementation will just set OutStatus := HTML_FORBIDDEN // and call Server.OnSessionFailed event procedure AuthenticationFailed; virtual; /// direct launch of a method-based service // - URI() will ensure that MethodIndex>=0 before calling it procedure ExecuteSOAByMethod; virtual; /// direct launch of an interface-based service // - URI() will ensure that Service<>nil before calling it // - abstract implementation which is to be overridden procedure ExecuteSOAByInterface; virtual; abstract; ................................................................................ TNotifyFieldSQLEvent = function(Sender: TSQLRestServer; Event: TSQLEvent; aTable: TSQLRecordClass; const aID: TID; const aAffectedFields: TSQLFieldBits): boolean of object; /// session-related callbacks triggered by TSQLRestServer // - for OnSessionCreate, returning TRUE will abort the session creation - // and you can set Ctxt.Call^.OutStatus to a corresponding error code TNotifySQLSession = function(Sender: TSQLRestServer; Session: TAuthSession; Ctxt: TSQLRestServerURIContext): boolean of object; TSQLRestStorageInMemory = class; TSQLVirtualTableModule = class; /// class-reference type (metaclass) of our abstract table storage // - may be e.g. TSQLRestStorageInMemory, TSQLRestStorageInMemoryExternal, // TSQLRestStorageExternal or TSQLRestStorageMongoDB ................................................................................ // TSQLAuthGroup instance for fast retrieval in TSQLRestServer.URI function GetUser(Ctxt: TSQLRestServerURIContext; const aUserName: RawUTF8): TSQLAuthUser; virtual; /// create a session on the server for a given user // - this default implementation will call fServer.SessionCreate() and // return a '{"result":"HEXASALT","logonname":"UserName"}' JSON content // and will always call User.Free procedure SessionCreate(Ctxt: TSQLRestServerURIContext; var User: TSQLAuthUser); virtual; /// abstract method which will be called by ClientSetUser() to process the // authentication step on the client side // - at call, a TSQLAuthUser instance will be supplied, with LogonName set // with aUserName and PasswordHashHexa with a SHA-256 hash of aPassword // - override with the expected method, returning the session key on success class function ClientComputeSessionKey(Sender: TSQLRestClientURI; User: TSQLAuthUser): RawUTF8; ................................................................................ /// this method is overridden for setting the NoAJAXJSON field // of all associated TSQLRestStorage servers procedure SetNoAJAXJSON(const Value: boolean); virtual; /// add a new session to the internal session list // - do not use this method directly: this callback is to be used by // TSQLRestServerAuthentication* classes // - will check that the logon name is valid procedure SessionCreate(var User: TSQLAuthUser; Ctxt: TSQLRestServerURIContext; out Session: TAuthSession); virtual; /// fill the supplied context from the supplied aContext.Session ID // - returns nil if not found, or fill aContext.User/Group values if matchs // - this method will also check for outdated sessions, and delete them // - this method is not thread-safe: caller should use fSessions.Lock function SessionAccess(Ctxt: TSQLRestServerURIContext): TAuthSession; ................................................................................ // (DenyOfService attack?) or the request is not valid (ManIntheMiddle attack?) // - e.g. if the URI signature is invalid, or OnSessionCreate event handler // aborted the session creation by returning TRUE (in this later case, // the Session parameter is not nil) // - you can access the current execution context from the Ctxt parameter, // e.g. to retrieve the caller's IP and ban aggressive users: // ! FindIniNameValue(pointer(Ctxt.Call^.InHead),'REMOTEIP: ') OnSessionFailed: TNotifySQLSession; /// a method can be specified to be notified when a session is closed // - for OnSessionClosed, the returning boolean value is ignored // - Ctxt is nil if the session is closed due to a timeout // - Ctxt is not nil if the session is closed explicitly by the client OnSessionClosed: TNotifySQLSession; /// this event will be executed to push notifications from the server to // a remote client, using a (fake) interface parameter ................................................................................ result := Server.fPublishedMethod[MethodIndex].ByPassAuthentication; end else begin // default unique session if authentication is not enabled Session := CONST_AUTHENTICATION_NOT_USED; result := true; end; end; procedure TSQLRestServerURIContext.AuthenticationFailed; begin // 401 Unauthorized response MUST include a WWW-Authenticate header, // which is not what we used, so here we won't send 401 error code but 403 Call.OutStatus := HTML_FORBIDDEN; // call the notification event if Assigned(Server.OnSessionFailed) then Server.OnSessionFailed(Server,nil,self); end; destructor TSQLRestAcquireExecution.Destroy; begin inherited Destroy; Thread.Free; end; ................................................................................ if (RootRedirectGet<>'') and (Ctxt.Method=mGet) and (Call.Url=Model.Root) and (Call.InBody='') then Ctxt.Redirect(RootRedirectGet) else begin Ctxt.URIDecodeSOAByMethod; if (Ctxt.MethodIndex<0) and (Ctxt.URI<>'') then Ctxt.URIDecodeSOAByInterface; // 2. handle security if (not Ctxt.Authenticate) or ((Ctxt.Service<>nil) and not (reService in Call.RestAccessRights^.AllowRemoteExecute)) then Ctxt.AuthenticationFailed else // 3. call appropriate ORM / SOA commands in fAcquireExecution[] context try if Ctxt.MethodIndex>=0 then if Ctxt.MethodIndex=fPublishedMethodBatchIndex then Ctxt.Command := execORMWrite else Ctxt.Command := execSOAByMethod else if Ctxt.Service<>nil then ................................................................................ for i := 0 to fSessions.Count-1 do if TAuthSession(fSessions.List[i]).User.fID=User.fID then begin {$ifdef WITHLOG} with TAuthSession(fSessions.List[i]) do Ctxt.Log.Log(sllUserAuth,'User.LogonName=% already connected from "%/%"', [User.LogonName,RemoteIP,Ctxt.Call^.LowLevelConnectionID],self); {$endif} Ctxt.Call^.OutStatus := HTML_NOTALLOWED; if Assigned(OnSessionFailed) then OnSessionFailed(self,nil,Ctxt); exit; // user already connected -> error 404 end; Session := fSessionClass.Create(Ctxt,User); if Assigned(OnSessionCreate) then if OnSessionCreate(self,Session,Ctxt) then begin {$ifdef WITHLOG} Ctxt.Log.Log(sllUserAuth,'Session aborted by OnSessionCreate() callback '+ 'for User.LogonName=% (connected from "%/%") - clients=%, sessions=%', [User.LogonName,Session.RemoteIP,Ctxt.Call^.LowLevelConnectionID, fStats.ClientsCurrent,fSessions.Count],self); {$endif} if Assigned(OnSessionFailed) then OnSessionFailed(self,Session,Ctxt); User := nil; FreeAndNil(Session); // returning TRUE aborts the session creation exit; end; User := nil; // will be freed by TAuthSession.Destroy fSessions.Add(Session); fStats.ClientConnect; end; ................................................................................ constructor TAuthSession.Create(aCtxt: TSQLRestServerURIContext; aUser: TSQLAuthUser); var GID: TSQLAuthGroup; begin fUser := aUser; if (aCtxt<>nil) and (User<>nil) and (User.fID<>0) then begin GID := User.GroupRights; // save pseudo TSQLAuthGroup = ID User.GroupRights := aCtxt.Server.fSQLAuthGroupClass.Create(aCtxt.Server,User.GroupRights); if User.GroupRights.fID<>0 then begin // compute the next Session ID with aCtxt.Server do begin if fSessionCounter>=cardinal(maxInt) then fSessionCounter := 10 else if fSessionCounter=75 then // avoid IDCardinal=0 (77) or 1 (76) fSessionCounter := 78 else ................................................................................ end; procedure TSQLRestServerAuthentication.SessionCreate(Ctxt: TSQLRestServerURIContext; var User: TSQLAuthUser); var Session: TAuthSession; begin if User<>nil then try // now client is authenticated -> create a session fServer.SessionCreate(User,Ctxt,Session); if Session<>nil then Ctxt.Returns(['result',Session.fPrivateSalt,'logonname',Session.User.LogonName]); finally User.Free; end; end; ................................................................................ aClientNonce := Ctxt.InputUTF8OrVoid['ClientNonce']; if (aUserName<>'') and (aClientNonce<>'') then begin // GET ModelRoot/auth?UserName=...&PassWord=...&ClientNonce=... -> handshaking User := GetUser(Ctxt,aUserName); if User<>nil then try // check if match TSQLRestClientURI.SetUser() algorithm if CheckPassword(Ctxt,User,aClientNonce,aPassWord) then begin // now client is authenticated -> create a session SessionCreate(Ctxt,User); exit; end; finally User.Free; end; Ctxt.AuthenticationFailed; 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 -> try next authentication result := false; end; ................................................................................ function TSQLRestServerAuthenticationNone.Auth(Ctxt: TSQLRestServerURIContext): boolean; var aUserName: RawUTF8; U: TSQLAuthUser; begin aUserName := Ctxt.InputUTF8OrVoid['UserName']; if aUserName='' then begin result := false; exit; end; result := true; if AuthSessionRelease(Ctxt) then exit; U := GetUser(Ctxt,aUserName); SessionCreate(Ctxt,U); end; class function TSQLRestServerAuthenticationNone.ClientComputeSessionKey( Sender: TSQLRestClientURI; User: TSQLAuthUser): RawUTF8; begin result := Sender.CallBackGetResult('Auth',['UserName',User.LogonName]); end; ................................................................................ if GetUserPassFromInHead(Ctxt,userPass,user,pass) then begin U := GetUser(Ctxt,user); if U<>nil then try expectedPass := U.PasswordHashHexa; U.PasswordPlain := pass; // override with SHA-256 hash from HTTP header if U.PasswordHashHexa=expectedPass then begin fServer.SessionCreate(U,Ctxt,Session); if Session<>nil then begin // see TSQLRestServerAuthenticationHttpAbstract.ClientSessionSign() Ctxt.SetOutSetCookie((COOKIE_SESSION+'=')+CardinalToHex(Session.IDCardinal)); Ctxt.Returns(['result',Session.IDCardinal,'logonname',Session.User.LogonName]); exit; // success end; end; finally U.Free; end; Ctxt.AuthenticationFailed; end else begin Ctxt.Call.OutHead := 'WWW-Authenticate: Basic realm="mORMot Server"';; Ctxt.Error('',HTML_UNAUTHORIZED); // will popup for credentials in browser end; end; ................................................................................ try 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)]); exit; end; finally User.Free; end; Ctxt.AuthenticationFailed; finally FreeSecContext(fSSPIAuthContexts[SecCtxIdx]); CtxArr.Delete(SecCtxIdx); end; end; class function TSQLRestServerAuthenticationSSPI.ClientComputeSessionKey( |
| > > > > > > > > > < > | > > > > > > > > > | | > | | | > | | | | < < | | | < | | < | | | < | < < > | | | | > > | | | > | | | | < < | | |
867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 .... 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 .... 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 ..... 12343 12344 12345 12346 12347 12348 12349 12350 12351 12352 12353 12354 12355 12356 12357 12358 12359 12360 12361 ..... 12634 12635 12636 12637 12638 12639 12640 12641 12642 12643 12644 12645 12646 12647 12648 12649 ..... 13402 13403 13404 13405 13406 13407 13408 13409 13410 13411 13412 13413 13414 13415 13416 13417 ..... 13485 13486 13487 13488 13489 13490 13491 13492 13493 13494 13495 13496 13497 13498 13499 ..... 32528 32529 32530 32531 32532 32533 32534 32535 32536 32537 32538 32539 32540 32541 32542 32543 32544 32545 32546 32547 32548 32549 32550 ..... 33891 33892 33893 33894 33895 33896 33897 33898 33899 33900 33901 33902 33903 33904 33905 33906 33907 33908 33909 ..... 34143 34144 34145 34146 34147 34148 34149 34150 34151 34152 34153 34154 34155 34156 34157 34158 34159 34160 34161 34162 34163 34164 34165 34166 34167 34168 34169 34170 34171 ..... 41953 41954 41955 41956 41957 41958 41959 41960 41961 41962 41963 41964 41965 41966 41967 ..... 42318 42319 42320 42321 42322 42323 42324 42325 42326 42327 42328 42329 42330 42331 42332 42333 ..... 42457 42458 42459 42460 42461 42462 42463 42464 42465 42466 42467 42468 42469 42470 42471 42472 42473 42474 42475 42476 42477 ..... 42507 42508 42509 42510 42511 42512 42513 42514 42515 42516 42517 42518 42519 42520 42521 42522 42523 42524 42525 42526 42527 42528 42529 42530 ..... 42657 42658 42659 42660 42661 42662 42663 42664 42665 42666 42667 42668 42669 42670 42671 42672 42673 42674 42675 42676 42677 42678 42679 42680 42681 42682 42683 ..... 42768 42769 42770 42771 42772 42773 42774 42775 42776 42777 42778 42779 42780 42781 42782 42783 42784 42785 42786 42787 42788 42789 42790 42791 42792 42793 42794 |
TSQLAuthUser and/or TSQLAuthGroup classes to store the authorization information: TSQLRestServer will search for any table inheriting from TSQLAuthUser/TSQLAuthGroup in the TSQLModel - see also corresponding TSQLRestServer.SQLAuthUserClass/SQLAuthGroupClass new properties - introducing TSQLAuthUser.CanUserLog() to ensure authentication is allowed, as requested by feature request [842906425928] - added TSynAuthenticationRest e.g. for SynDBRemote to check REST users - added TSQLRestServer.OnSessionCreate/OnSessionClosed/OnAuthenticationFailed callbacks, and TSQLRestServerURIContext.AuthenticationFailed virtual method - added TSQLRestServer.SessionClass property to specify the class type to handle in-memory sessions, and override e.g. IsValidURI() method - CreateMissingTables() method is not declared as virtual in TSQLRestServer - TSQLRestServer.URI() and TSQLRestClientURI.InternalURI() methods now uses one TSQLRestURIParams parameter for all request input and output values - TSQLRestServer.URI() method will return "405 Method Not Allowed" error ................................................................................ // ! context := @ServiceContext; // threadvar access once // ! ... PServiceRunningContext = ^TServiceRunningContext; TSQLRestServerURIContext = class; TAuthSession = class; /// used to identify the authentication failure reason // - as transmitted e.g. by TSQLRestServerURIContext.AuthenticationFailed or // TSQLRestServer.OnAuthenticationFailed TNotifyAuthenticationFailedReason = ( afInvalidSignature,afRemoteServiceExecutionNotAllowed, afUnknownUser,afInvalidPassword, afSessionAlreadyStartedForThisUser,afSessionCreationAborted); /// will identify the currently running service on the server side // - is the type of the global ServiceContext threadvar // - to access the current TSQLRestServer instance (and e.g. its ORM/CRUD // or SOA methods), use Request.Server and not Factory.Server, which may not // be available e.g. if you run the service from the server side (so no // factory is involved) TServiceRunningContext = record ................................................................................ // - should set Service member (and possibly ServiceMethodIndex) // - abstract implementation which is to be overridden procedure URIDecodeSOAByInterface; virtual; abstract; /// process authentication // - return FALSE in case of invalid signature, TRUE if authenticated function Authenticate: boolean; virtual; /// method called in case of authentication failure // - the failure origin is stated by the Reason parameter // - this default implementation will just set OutStatus := HTML_FORBIDDEN // and call TSQLRestServer.OnAuthenticationFailed event (if any) procedure AuthenticationFailed(Reason: TNotifyAuthenticationFailedReason); virtual; /// direct launch of a method-based service // - URI() will ensure that MethodIndex>=0 before calling it procedure ExecuteSOAByMethod; virtual; /// direct launch of an interface-based service // - URI() will ensure that Service<>nil before calling it // - abstract implementation which is to be overridden procedure ExecuteSOAByInterface; virtual; abstract; ................................................................................ TNotifyFieldSQLEvent = function(Sender: TSQLRestServer; Event: TSQLEvent; aTable: TSQLRecordClass; const aID: TID; const aAffectedFields: TSQLFieldBits): boolean of object; /// session-related callbacks triggered by TSQLRestServer // - for OnSessionCreate, returning TRUE will abort the session creation - // and you can set Ctxt.Call^.OutStatus to a corresponding error code TNotifySQLSession = function(Sender: TSQLRestServer; Session: TAuthSession; Ctxt: TSQLRestServerURIContext): boolean of object; /// callback raised in case of authentication failure // - as used by TSQLRestServerURIContext.AuthenticationFailed event TNotifyAuthenticationFailed = procedure(Sender: TSQLRestServer; Reason: TNotifyAuthenticationFailedReason; Session: TAuthSession; Ctxt: TSQLRestServerURIContext) of object; TSQLRestStorageInMemory = class; TSQLVirtualTableModule = class; /// class-reference type (metaclass) of our abstract table storage // - may be e.g. TSQLRestStorageInMemory, TSQLRestStorageInMemoryExternal, // TSQLRestStorageExternal or TSQLRestStorageMongoDB ................................................................................ // TSQLAuthGroup instance for fast retrieval in TSQLRestServer.URI function GetUser(Ctxt: TSQLRestServerURIContext; const aUserName: RawUTF8): TSQLAuthUser; virtual; /// create a session on the server for a given user // - this default implementation will call fServer.SessionCreate() and // return a '{"result":"HEXASALT","logonname":"UserName"}' JSON content // and will always call User.Free // - on failure, will call TSQLRestServerURIContext.AuthenticationFailed() // with afSessionAlreadyStartedForThisUser or afSessionCreationAborted reason procedure SessionCreate(Ctxt: TSQLRestServerURIContext; var User: TSQLAuthUser); virtual; /// abstract method which will be called by ClientSetUser() to process the // authentication step on the client side // - at call, a TSQLAuthUser instance will be supplied, with LogonName set // with aUserName and PasswordHashHexa with a SHA-256 hash of aPassword // - override with the expected method, returning the session key on success class function ClientComputeSessionKey(Sender: TSQLRestClientURI; User: TSQLAuthUser): RawUTF8; ................................................................................ /// this method is overridden for setting the NoAJAXJSON field // of all associated TSQLRestStorage servers procedure SetNoAJAXJSON(const Value: boolean); virtual; /// add a new session to the internal session list // - do not use this method directly: this callback is to be used by // TSQLRestServerAuthentication* classes // - will check that the logon name is valid // - on failure, will call TSQLRestServerURIContext.AuthenticationFailed() // with afSessionAlreadyStartedForThisUser or afSessionCreationAborted reason procedure SessionCreate(var User: TSQLAuthUser; Ctxt: TSQLRestServerURIContext; out Session: TAuthSession); virtual; /// fill the supplied context from the supplied aContext.Session ID // - returns nil if not found, or fill aContext.User/Group values if matchs // - this method will also check for outdated sessions, and delete them // - this method is not thread-safe: caller should use fSessions.Lock function SessionAccess(Ctxt: TSQLRestServerURIContext): TAuthSession; ................................................................................ // (DenyOfService attack?) or the request is not valid (ManIntheMiddle attack?) // - e.g. if the URI signature is invalid, or OnSessionCreate event handler // aborted the session creation by returning TRUE (in this later case, // the Session parameter is not nil) // - you can access the current execution context from the Ctxt parameter, // e.g. to retrieve the caller's IP and ban aggressive users: // ! FindIniNameValue(pointer(Ctxt.Call^.InHead),'REMOTEIP: ') OnAuthenticationFailed: TNotifyAuthenticationFailed; /// a method can be specified to be notified when a session is closed // - for OnSessionClosed, the returning boolean value is ignored // - Ctxt is nil if the session is closed due to a timeout // - Ctxt is not nil if the session is closed explicitly by the client OnSessionClosed: TNotifySQLSession; /// this event will be executed to push notifications from the server to // a remote client, using a (fake) interface parameter ................................................................................ result := Server.fPublishedMethod[MethodIndex].ByPassAuthentication; end else begin // default unique session if authentication is not enabled Session := CONST_AUTHENTICATION_NOT_USED; result := true; end; end; procedure TSQLRestServerURIContext.AuthenticationFailed( Reason: TNotifyAuthenticationFailedReason); begin // 401 Unauthorized response MUST include a WWW-Authenticate header, // which is not what we used, so here we won't send 401 error code but 403 Call.OutStatus := HTML_FORBIDDEN; // call the notification event if Assigned(Server.OnAuthenticationFailed) then Server.OnAuthenticationFailed(Server,Reason,nil,self); end; destructor TSQLRestAcquireExecution.Destroy; begin inherited Destroy; Thread.Free; end; ................................................................................ if (RootRedirectGet<>'') and (Ctxt.Method=mGet) and (Call.Url=Model.Root) and (Call.InBody='') then Ctxt.Redirect(RootRedirectGet) else begin Ctxt.URIDecodeSOAByMethod; if (Ctxt.MethodIndex<0) and (Ctxt.URI<>'') then Ctxt.URIDecodeSOAByInterface; // 2. handle security if not Ctxt.Authenticate then Ctxt.AuthenticationFailed(afInvalidSignature) else if (Ctxt.Service<>nil) and not (reService in Call.RestAccessRights^.AllowRemoteExecute) then Ctxt.AuthenticationFailed(afRemoteServiceExecutionNotAllowed) else // 3. call appropriate ORM / SOA commands in fAcquireExecution[] context try if Ctxt.MethodIndex>=0 then if Ctxt.MethodIndex=fPublishedMethodBatchIndex then Ctxt.Command := execORMWrite else Ctxt.Command := execSOAByMethod else if Ctxt.Service<>nil then ................................................................................ for i := 0 to fSessions.Count-1 do if TAuthSession(fSessions.List[i]).User.fID=User.fID then begin {$ifdef WITHLOG} with TAuthSession(fSessions.List[i]) do Ctxt.Log.Log(sllUserAuth,'User.LogonName=% already connected from "%/%"', [User.LogonName,RemoteIP,Ctxt.Call^.LowLevelConnectionID],self); {$endif} Ctxt.AuthenticationFailed(afSessionAlreadyStartedForThisUser); exit; // user already connected end; Session := fSessionClass.Create(Ctxt,User); if Assigned(OnSessionCreate) then if OnSessionCreate(self,Session,Ctxt) then begin // TRUE aborts session creation {$ifdef WITHLOG} Ctxt.Log.Log(sllUserAuth,'Session aborted by OnSessionCreate() callback '+ 'for User.LogonName=% (connected from "%/%") - clients=%, sessions=%', [User.LogonName,Session.RemoteIP,Ctxt.Call^.LowLevelConnectionID, fStats.ClientsCurrent,fSessions.Count],self); {$endif} Ctxt.AuthenticationFailed(afSessionCreationAborted); User := nil; FreeAndNil(Session); exit; end; User := nil; // will be freed by TAuthSession.Destroy fSessions.Add(Session); fStats.ClientConnect; end; ................................................................................ constructor TAuthSession.Create(aCtxt: TSQLRestServerURIContext; aUser: TSQLAuthUser); var GID: TSQLAuthGroup; begin fUser := aUser; if (aCtxt<>nil) and (User<>nil) and (User.fID<>0) then begin GID := User.GroupRights; // save pseudo TSQLAuthGroup = ID User.GroupRights := aCtxt.Server.fSQLAuthGroupClass.Create(aCtxt.Server,GID); if User.GroupRights.fID<>0 then begin // compute the next Session ID with aCtxt.Server do begin if fSessionCounter>=cardinal(maxInt) then fSessionCounter := 10 else if fSessionCounter=75 then // avoid IDCardinal=0 (77) or 1 (76) fSessionCounter := 78 else ................................................................................ end; procedure TSQLRestServerAuthentication.SessionCreate(Ctxt: TSQLRestServerURIContext; var User: TSQLAuthUser); var Session: TAuthSession; begin if User<>nil then try // now client is authenticated -> create a session fServer.SessionCreate(User,Ctxt,Session); // call Ctxt.AuthenticationFailed on error if Session<>nil then Ctxt.Returns(['result',Session.fPrivateSalt,'logonname',Session.User.LogonName]); finally User.Free; end; end; ................................................................................ aClientNonce := Ctxt.InputUTF8OrVoid['ClientNonce']; if (aUserName<>'') and (aClientNonce<>'') then begin // GET ModelRoot/auth?UserName=...&PassWord=...&ClientNonce=... -> handshaking User := GetUser(Ctxt,aUserName); if User<>nil then try // check if match TSQLRestClientURI.SetUser() algorithm if CheckPassword(Ctxt,User,aClientNonce,aPassWord) then SessionCreate(Ctxt,User) else // will call Ctxt.AuthenticationFailed on error Ctxt.AuthenticationFailed(afInvalidPassword); finally User.Free; end else Ctxt.AuthenticationFailed(afUnknownUser); 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 -> try next authentication result := false; end; ................................................................................ function TSQLRestServerAuthenticationNone.Auth(Ctxt: TSQLRestServerURIContext): boolean; var aUserName: RawUTF8; U: TSQLAuthUser; begin aUserName := Ctxt.InputUTF8OrVoid['UserName']; if aUserName='' then begin result := false; // let's try another TSQLRestServerAuthentication class exit; end; result := true; // this kind of weak authentication avoid stronger ones if AuthSessionRelease(Ctxt) then exit; U := GetUser(Ctxt,aUserName); if U=nil then Ctxt.AuthenticationFailed(afUnknownUser) else SessionCreate(Ctxt,U); // call Ctxt.AuthenticationFailed on error end; class function TSQLRestServerAuthenticationNone.ClientComputeSessionKey( Sender: TSQLRestClientURI; User: TSQLAuthUser): RawUTF8; begin result := Sender.CallBackGetResult('Auth',['UserName',User.LogonName]); end; ................................................................................ if GetUserPassFromInHead(Ctxt,userPass,user,pass) then begin U := GetUser(Ctxt,user); if U<>nil then try expectedPass := U.PasswordHashHexa; U.PasswordPlain := pass; // override with SHA-256 hash from HTTP header if U.PasswordHashHexa=expectedPass then begin fServer.SessionCreate(U,Ctxt,Session); // call Ctxt.AuthenticationFailed on error if Session<>nil then begin // see TSQLRestServerAuthenticationHttpAbstract.ClientSessionSign() Ctxt.SetOutSetCookie((COOKIE_SESSION+'=')+CardinalToHex(Session.IDCardinal)); Ctxt.Returns(['result',Session.IDCardinal,'logonname',Session.User.LogonName]); exit; // success end; end else Ctxt.AuthenticationFailed(afInvalidPassword); finally U.Free; end else Ctxt.AuthenticationFailed(afUnknownUser); end else begin Ctxt.Call.OutHead := 'WWW-Authenticate: Basic realm="mORMot Server"';; Ctxt.Error('',HTML_UNAUTHORIZED); // will popup for credentials in browser end; end; ................................................................................ try if UserName='' then exit; User := GetUser(Ctxt,UserName); if User<>nil then try User.PasswordHashHexa := ''; // override with context fServer.SessionCreate(User,Ctxt,Session); // call Ctxt.AuthenticationFailed on error 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 else Ctxt.AuthenticationFailed(afUnknownUser); finally FreeSecContext(fSSPIAuthContexts[SecCtxIdx]); CtxArr.Delete(SecCtxIdx); end; end; class function TSQLRestServerAuthenticationSSPI.ClientComputeSessionKey( |
Changes to SynopseCommit.inc.
1 |
'1.18.1258'
|
| |
1 |
'1.18.1259'
|