delphimvcframework/sources/MVCFramework.Middleware.Authentication.pas
2015-04-01 15:01:23 +00:00

197 lines
6.1 KiB
ObjectPascal
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

unit MVCFramework.Middleware.Authentication;
interface
uses
MVCFramework, MVCFramework.Commons, MVCFramework.Logger, System.Generics.Collections;
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);
procedure OnBeforeControllerAction(Context: TWebContext;
const AControllerQualifiedClassName: string; const AActionName: string; var Handled: Boolean);
public
constructor Create(AMVCAuthenticationHandler: IMVCAuthenticationHandler;
Realm: string = 'DelphiMVCFramework REALM'); virtual;
end;
implementation
uses
System.SysUtils, MVCFramework.Session, Soap.EncdDecd;
{
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 isnt 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);
begin
end;
procedure TMVCBasicAuthenticationMiddleware.OnBeforeControllerAction(Context: TWebContext;
const AControllerQualifiedClassName, AActionName: string; var Handled: Boolean);
var
LAuth: string;
LPieces: TArray<string>;
LRoles: TList<string>;
LIsValid: Boolean;
LWebSession: TWebSession;
LSessionID: string;
LIsAuthorized: Boolean;
LSessionIDFromWebRequest: string;
LAuthRequired: Boolean;
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);
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);
if not LAuthRequired then
begin
Handled := False;
Exit;
end;
LSessionIDFromWebRequest := TMVCEngine.ExtractSessionIDFromWebRequest
(Context.Request.RawWebRequest);
LWebSession := TMVCEngine.GetCurrentSession(Context.Config.AsInt64[TMVCConfigKey.SessionTimeout],
LSessionIDFromWebRequest, False);
if (not LSessionIDFromWebRequest.IsEmpty) and (not Assigned(LWebSession)) then
begin
// 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;
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
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
FMVCAuthenticationHandler.OnAuthentication(LPieces[0], LPieces[1], LRoles, LIsValid);
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);
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);
begin
end;
end.