delphimvcframework/sources/MVCFramework.Middleware.Authentication.pas

243 lines
7.6 KiB
ObjectPascal
Raw Normal View History

{ *************************************************************************** }
{ }
{ Delphi MVC Framework }
{ }
{ Copyright (c) 2010-2015 Daniele Teti and the DMVCFramework Team }
{ }
{ https://github.com/danieleteti/delphimvcframework }
{ }
{ *************************************************************************** }
{ }
{ Licensed under the Apache License, Version 2.0 (the "License"); }
{ you may not use this file except in compliance with the License. }
{ You may obtain a copy of the License at }
{ }
{ http://www.apache.org/licenses/LICENSE-2.0 }
{ }
{ Unless required by applicable law or agreed to in writing, software }
{ distributed under the License is distributed on an "AS IS" BASIS, }
{ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. }
{ See the License for the specific language governing permissions and }
{ limitations under the License. }
{ }
{ *************************************************************************** }
2015-12-22 12:38:17 +01:00
2015-04-01 17:01:23 +02:00
unit MVCFramework.Middleware.Authentication;
interface
uses
MVCFramework, MVCFramework.Commons, MVCFramework.Logger,
System.Generics.Collections;
2015-04-01 17:01:23 +02:00
type
TMVCBasicAuthenticationMiddleware = class(TInterfacedObject, IMVCMiddleware)
strict private
FMVCAuthenticationHandler: IMVCAuthenticationHandler;
protected
FRealm: string;
procedure OnBeforeRouting(Context: TWebContext; var Handled: Boolean);
procedure OnAfterControllerAction(Context: TWebContext;
const AActionName: string; const Handled: Boolean);
2015-04-01 17:01:23 +02:00
procedure OnBeforeControllerAction(Context: TWebContext;
const AControllerQualifiedClassName: string; const AActionName: string;
var Handled: Boolean);
2015-04-01 17:01:23 +02:00
public
constructor Create(AMVCAuthenticationHandler: IMVCAuthenticationHandler;
Realm: string = 'DelphiMVCFramework REALM'); virtual;
end;
implementation
uses
2015-10-18 16:35:50 +02:00
System.SysUtils, MVCFramework.Session, Soap.EncdDecd,
System.NetEncoding;
2015-04-01 17:01:23 +02:00
{
401 Unauthorized response should be used for missing or bad authentication, and a
403 Forbidden response should be used afterwards, when the user is authenticated
but isn<EFBFBD>t authorized to perform the requested operation on the given resource.
}
const
CONTENT_HTML_FORMAT = '<html><body><h1>%s</h1></body></html>';
CONTENT_401_NOT_AUTHORIZED = '401: Not authorized';
CONTENT_403_FORBIDDEN = '403: Forbidden';
{ TMVCSalutationMiddleware }
constructor TMVCBasicAuthenticationMiddleware.Create(AMVCAuthenticationHandler
: IMVCAuthenticationHandler; Realm: string);
begin
inherited Create;
FMVCAuthenticationHandler := AMVCAuthenticationHandler;
FRealm := Realm;
end;
procedure TMVCBasicAuthenticationMiddleware.OnAfterControllerAction
(Context: TWebContext; const AActionName: string; const Handled: Boolean);
2015-04-01 17:01:23 +02:00
begin
end;
procedure TMVCBasicAuthenticationMiddleware.OnBeforeControllerAction
(Context: TWebContext; const AControllerQualifiedClassName,
AActionName: string; var Handled: Boolean);
2015-04-01 17:01:23 +02:00
var
LAuth: string;
LPieces: TArray<string>;
LRoles: TList<string>;
LIsValid: Boolean;
LWebSession: TWebSession;
LSessionID: string;
LIsAuthorized: Boolean;
LSessionIDFromWebRequest: string;
LAuthRequired: Boolean;
LSessionData: TSessionData;
LPair: TPair<String, String>;
2015-04-01 17:01:23 +02:00
procedure SendWWWAuthenticate;
begin
Context.LoggedUser.Clear;
if Context.Request.ClientPreferHTML then
begin
Context.Response.ContentType := 'text/html';
Context.Response.RawWebResponse.Content :=
Format(CONTENT_HTML_FORMAT, [CONTENT_401_NOT_AUTHORIZED]);
end
else
begin
Context.Response.ContentType := 'text/plain';
Context.Response.RawWebResponse.Content := CONTENT_401_NOT_AUTHORIZED;
end;
Context.Response.StatusCode := 401;
Context.Response.SetCustomHeader('WWW-Authenticate',
'Basic realm=' + FRealm.QuotedString);
2015-04-01 17:01:23 +02:00
Handled := true;
end;
procedure Send403Forbidden;
begin
Context.LoggedUser.Clear;
if Context.Request.ClientPreferHTML then
begin
Context.Response.ContentType := 'text/html';
Context.Response.RawWebResponse.Content :=
Format(CONTENT_HTML_FORMAT, [CONTENT_403_FORBIDDEN]);
end
else
begin
Context.Response.ContentType := 'text/plain';
Context.Response.RawWebResponse.Content := CONTENT_403_FORBIDDEN;
end;
Context.Response.StatusCode := 403;
Handled := true;
end;
begin
// check if the resource is protected
FMVCAuthenticationHandler.OnRequest(AControllerQualifiedClassName,
AActionName, LAuthRequired);
2015-04-01 17:01:23 +02:00
if not LAuthRequired then
begin
Handled := False;
Exit;
end;
LSessionIDFromWebRequest := TMVCEngine.ExtractSessionIDFromWebRequest
(Context.Request.RawWebRequest);
LWebSession := TMVCEngine.GetCurrentSession
(Context.Config.AsInt64[TMVCConfigKey.SessionTimeout],
2015-04-01 17:01:23 +02:00
LSessionIDFromWebRequest, False);
// if (not LSessionIDFromWebRequest.IsEmpty) and (not Assigned(LWebSession)) then
// begin
// SendWWWAuthenticate;
// // Exit;
// // The sessionid is present but is not valid and there is an authentication header.
// // In this case, an exception is raised because the sessionid is not valid
// // raise EMVCSessionExpiredException.Create('Session expired');
// end;
2015-04-01 17:01:23 +02:00
Context.LoggedUser.LoadFromSession(LWebSession);
if not Context.LoggedUser.IsValid then
begin
// check if the resource is protected
// FMVCAuthenticationHandler.OnRequest(AControllerQualifiedClassName, AActionName, LAuthRequired);
// if not LAuthRequired then
// begin
// Handled := False;
// Exit;
// end;
// We NEED authentication
2015-04-01 17:01:23 +02:00
LAuth := Context.Request.Headers['Authorization'];
LAuth := DecodeString(LAuth.Remove(0, 'Basic'.Length).Trim);
LPieces := LAuth.Split([':']);
if LAuth.IsEmpty or (Length(LPieces) <> 2) then
begin
SendWWWAuthenticate;
Exit;
end;
// now, we have username and password.
// check the authorization for the requested resource
LRoles := TList<string>.Create;
try
LSessionData := TSessionData.Create;
try
FMVCAuthenticationHandler.OnAuthentication(LPieces[0], LPieces[1],
LRoles, LIsValid, LSessionData);
if LIsValid then
begin
Context.LoggedUser.Roles.AddRange(LRoles);
Context.LoggedUser.UserName := LPieces[0];
Context.LoggedUser.LoggedSince := Now;
Context.LoggedUser.Realm := FRealm;
LSessionID := TMVCEngine.SendSessionCookie(Context);
LWebSession := TMVCEngine.AddSessionToTheSessionList(LSessionID,
Context.Config.AsInt64[TMVCConfigKey.SessionTimeout]);
Context.LoggedUser.SaveToSession(LWebSession);
// save sessiondata to the actual session
for LPair in LSessionData do
begin
LWebSession[LPair.Key] := LPair.Value;
end;
end;
finally
LSessionData.Free;
2015-04-01 17:01:23 +02:00
end;
finally
LRoles.Free;
end;
end;
// authorization
LIsAuthorized := False;
if LIsValid then
begin
FMVCAuthenticationHandler.OnAuthorization(Context.LoggedUser.Roles,
AControllerQualifiedClassName, AActionName, LIsAuthorized)
end;
if LIsAuthorized then
Handled := False
else
begin
if LIsValid then
Send403Forbidden
else
SendWWWAuthenticate;
end;
end;
procedure TMVCBasicAuthenticationMiddleware.OnBeforeRouting
(Context: TWebContext; var Handled: Boolean);
2015-04-01 17:01:23 +02:00
begin
end;
end.