mirror of
https://github.com/danieleteti/delphimvcframework.git
synced 2024-11-16 00:05:53 +01:00
1ef246a589
- TWebContext param in the actions is optional - In case of "action not found", the server dont returns NEVER the document index - Refactoring - More unit tests - BasicDemo updated with typed actions - Copyright updated (just formatting)
228 lines
6.9 KiB
ObjectPascal
228 lines
6.9 KiB
ObjectPascal
// ***************************************************************************
|
||
//
|
||
// Delphi MVC Framework
|
||
//
|
||
// Copyright (c) 2010-2016 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.
|
||
//
|
||
// *************************************************************************** }
|
||
|
||
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
|
||
{$IF CompilerVersion >= 21}
|
||
, System.NetEncoding
|
||
{$ELSE}
|
||
, Soap.EncdDecd
|
||
{$ENDIF};
|
||
|
||
{
|
||
|
||
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<73>t authorized to perform the requested operation on the given resource.
|
||
|
||
}
|
||
|
||
const
|
||
CONTENT_HTML_FORMAT = '<html><body><h1>%s</h1><p>%s</p></body></html>';
|
||
CONTENT_401_NOT_AUTHORIZED = '401: Not authorized';
|
||
CONTENT_403_FORBIDDEN = '403: Forbidden';
|
||
|
||
function Base64DecodeString(const Value: String): String; inline;
|
||
begin
|
||
{$IF CompilerVersion >= 21}
|
||
Result := TNetEncoding.Base64.Decode(Value);
|
||
{$ELSE}
|
||
Result := DecodeString(Value);
|
||
{$ENDIF}
|
||
end;
|
||
|
||
{ 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
|
||
// do nothing
|
||
end;
|
||
|
||
procedure TMVCBasicAuthenticationMiddleware.OnBeforeControllerAction
|
||
(Context: TWebContext; const AControllerQualifiedClassName,
|
||
AActionName: string; var Handled: Boolean);
|
||
var
|
||
LAuth: string;
|
||
LPieces: TArray<string>;
|
||
LRoles: TList<string>;
|
||
LIsValid: Boolean;
|
||
LIsAuthorized: Boolean;
|
||
LAuthRequired: Boolean;
|
||
LSessionData: TSessionData;
|
||
LPair: TPair<String, String>;
|
||
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, Context.Config[TMVCConfigKey.ServerName]]);
|
||
end
|
||
else
|
||
begin
|
||
Context.Response.ContentType := 'text/plain';
|
||
Context.Response.RawWebResponse.Content := CONTENT_401_NOT_AUTHORIZED + sLineBreak + Context.Config[TMVCConfigKey.ServerName];
|
||
end;
|
||
Context.Response.StatusCode := 401;
|
||
Context.Response.SetCustomHeader('WWW-Authenticate',
|
||
'Basic realm=' + QuotedStr(FRealm));
|
||
|
||
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, Context.Config[TMVCConfigKey.ServerName]]);
|
||
end
|
||
else
|
||
begin
|
||
Context.Response.ContentType := 'text/plain';
|
||
Context.Response.RawWebResponse.Content := CONTENT_403_FORBIDDEN + sLineBreak + Context.Config[TMVCConfigKey.ServerName];
|
||
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;
|
||
|
||
Context.LoggedUser.LoadFromSession(Context.Session);
|
||
if not Context.LoggedUser.IsValid then
|
||
begin
|
||
// We NEED authentication
|
||
LAuth := Context.Request.Headers['Authorization'];
|
||
LAuth := Base64DecodeString(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;
|
||
Context.LoggedUser.SaveToSession(Context.Session);
|
||
|
||
// save sessiondata to the actual session
|
||
for LPair in LSessionData do
|
||
begin
|
||
Context.Session[LPair.Key] := LPair.Value;
|
||
end;
|
||
end;
|
||
finally
|
||
LSessionData.Free;
|
||
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
|
||
// do nothing
|
||
end;
|
||
|
||
end.
|