FIX JWT (ported from 2.x branch (247f25e519)

SSE Sample updated
This commit is contained in:
Daniele Teti 2017-07-05 00:17:46 +02:00
parent d6a04c89c2
commit f108357a92
9 changed files with 228 additions and 92 deletions

View File

@ -34,7 +34,7 @@ implementation
uses
MVCFramework.RESTClient, ObjectsMappers,
MVCFramework.RESTClient,
MVCFramework.SystemJSONUtils,
MVCFramework.TypesAliases;
@ -80,7 +80,7 @@ begin
.Header('jwtusername', 'user1')
.Header('jwtpassword', 'user1');
lRest := lClient.doPOST('/login', []);
lJSON := TSystemJSON.BodyAsJSONObject(lRest);
lJSON := TSystemJSON.StringAsJSONObject(lRest.BodyAsString);
try
JWT := lJSON.GetValue('token').Value;
finally

View File

@ -45,7 +45,8 @@ begin
// setting up the correct SSE headers
ContentType := 'text/event-stream';
Context.Response.SetCustomHeader('Cache-Control', 'no-cache');
Context.Response.SetCustomHeader('Connection', 'keep-alive');
// WARNING!! keep-alive heaer has been set directly on the server!
// Context.Response.SetCustomHeader('Connection', 'keep-alive');
// render the response using SSE compliant data format
@ -58,7 +59,6 @@ begin
// before trying to reconnect.
ResponseStream.Append('retry: 100'#13);
ResponseStream.Append('event: stockupdate'#13);
// actual message

View File

@ -30,6 +30,7 @@ begin
Writeln(Format('Starting HTTP Server on port %d', [APort]));
LServer := TIdHTTPWebBrokerBridge.Create(nil);
try
LServer.KeepAlive := True;
LServer.DefaultPort := APort;
LServer.Active := True;
LogI(Format('Server started on port 8080', [APort]));

View File

@ -6,9 +6,10 @@
<title>Server Sent Events DelphiMVCFramework Example - Stock Tickets</title>
<style media="screen" type="text/css">
body {
font-family: "Verdana", Tahoma, serif;
}
body {
font-family: "Verdana", Tahoma, serif;
}
H1 {
text-align: center;
font-size: 150%;

View File

@ -411,6 +411,10 @@ function B64Encode(const AValue: string): string; overload;
function B64Encode(const AValue: TBytes): string; overload;
function B64Decode(const AValue: string): string;
function URLSafeB64encode(const Value: string; IncludePadding: Boolean): String; overload;
function URLSafeB64encode(const Value: TBytes; IncludePadding: Boolean): String; overload;
function URLSafeB64Decode(const Value: string): String;
function ByteToHex(AInByte: Byte): string;
function BytesToHex(ABytes: TBytes): string;
@ -419,6 +423,9 @@ var
implementation
uses
IdCoder3to4;
const
RESERVED_IPS: array [1 .. 11] of array [1 .. 2] of string =
(('0.0.0.0', '0.255.255.255'), ('10.0.0.0', '10.255.255.255'),
@ -455,16 +462,19 @@ end;
function B64Encode(const AValue: string): string; overload;
begin
//Do not use TNetEncoding
Result := TIdEncoderMIME.EncodeString(AValue);
end;
function B64Encode(const AValue: TBytes): string; overload;
begin
//Do not use TNetEncoding
Result := TIdEncoderMIME.EncodeBytes(TIdBytes(AValue));
end;
function B64Decode(const AValue: string): string;
begin
//Do not use TNetEncoding
Result := TIdDecoderMIME.DecodeString(AValue);
end;
@ -712,10 +722,98 @@ begin
end;
end;
type
TURLSafeEncode = class(TIdEncoder3to4)
protected
procedure InitComponent; override;
public
end;
TURLSafeDecode = class(TIdDecoder4to3)
protected
class var GSafeBaseBase64DecodeTable: TIdDecodeTable;
procedure InitComponent; override;
public
end;
const
GURLSafeBase64CodeTable: string =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; {Do not Localize}
procedure TURLSafeEncode.InitComponent;
begin
inherited;
FCodingTable := ToBytes(GURLSafeBase64CodeTable);
FFillChar := '='; {Do not Localize}
end;
procedure TURLSafeDecode.InitComponent;
begin
inherited;
FDecodeTable := GSafeBaseBase64DecodeTable;
FCodingTable := ToBytes(GURLSafeBase64CodeTable);
FFillChar := '='; {Do not Localize}
end;
function URLSafeB64encode(const Value: string; IncludePadding: Boolean): String; overload;
begin
if IncludePadding then
Result := TURLSafeEncode.EncodeString(Value)
else
Result := TURLSafeEncode.EncodeString(Value).Replace('=', '', [rfReplaceAll]);
end;
/// <summary>
/// Remove "trimmed" character from the end of the string passed as parameter
/// </summary>
/// <param name="Value">Original string</param>
/// <param name="TrimmedChar">Character to remove</param>
/// <returns>Resulting string</returns>
function RTrim(const Value: string; TrimmedChar: char): string;
var
Strlen: Integer;
begin
Strlen := Length(Value);
while (Strlen>0) and (Value[Strlen]=TrimmedChar) do
dec(StrLen);
result := copy(value, 1, StrLen)
end;
function URLSafeB64encode(const Value: TBytes; IncludePadding: Boolean): String; overload;
begin
if IncludePadding then
Result := TURLSafeEncode.EncodeBytes(TIdBytes(Value))
else
Result := RTrim(TURLSafeEncode.EncodeBytes(TIdBytes(Value)), '=');
end;
function URLSafeB64Decode(const Value: string): String;
begin
// SGR 2017-07-03 : b64url might not include padding. Need to add it before decoding
case Length(value) mod 4 of
0:
begin
Result := TURLSafeDecode.DecodeString(Value);
end;
2:
Result := TURLSafeDecode.DecodeString(Value + '==');
3:
Result := TURLSafeDecode.DecodeString(Value + '=');
else
raise EExternalException.Create('Illegal base64url length');
end;
end;
initialization
Lock := TObject.Create;
// SGR 2017-07-03 : Initialize decoding table for URLSafe Gb64 encoding
TURLSafeDecode.ConstructDecodeTable(GURLSafeBase64CodeTable, TURLSafeDecode.GSafeBaseBase64DecodeTable);
GlobalAppExe := ExtractFileName(GetModuleName(HInstance));
GlobalAppName := ChangeFileExt(GlobalAppExe, EmptyStr);
GlobalAppPath := IncludeTrailingPathDelimiter(ExtractFilePath(GetModuleName(HInstance)));

View File

@ -24,91 +24,114 @@
unit MVCFramework.HMAC;
{$I dmvcframework.inc}
interface
uses
System.SysUtils,
System.Generics.Collections,
EncdDecd,
IdHMAC;
type
EMVCHMACException = class(Exception)
private
{ private declarations }
protected
{ protected declarations }
public
{ public declarations }
end;
THMACClass = class of TIdHMAC;
IHMAC = interface
['{95134024-BBAF-4E52-A4C3-54189672E18A}']
function HashValue(const Input, key: String): TBytes;
end;
function HMAC(const AAlgorithm: String; const AInput, AKey: string): TBytes;
procedure RegisterHMACAlgorithm(const AAlgorithm: String; AClazz: THMACClass);
procedure UnRegisterHMACAlgorithm(const AAlgorithm: String);
function HMAC(const Algorithm: String; const Input, Key: string): TBytes;
procedure RegisterHMACAlgorithm(const Algorithm: String; Impl: IHMAC);
procedure UnRegisterHMACAlgorithm(const Algorithm: String);
implementation
uses
IdSSLOpenSSL,
IdHash,
IdGlobal,
IdHMACMD5,
IdHMACSHA1;
IdSSLOpenSSL, IdHash, IdGlobal, IdHMACMD5,
IdHMACSHA1, System.Generics.Collections;
var
GHMACRegistry: TDictionary<string, THMACClass>;
GHMACRegistry: TDictionary<string, IHMAC>;
function HMAC(const AAlgorithm: String; const AInput, AKey: string): TBytes;
var
LHMAC: TIdHMAC;
begin
if not GHMACRegistry.ContainsKey(AAlgorithm) then
raise EMVCHMACException.CreateFmt('Unknown HMAC algorithm [%s]', [AAlgorithm]);
type
THMACClass = class of TIdHMAC;
TIdHMACWrapper = class(TInterfacedObject, IHMAC)
private
FClass: THMACClass;
public
constructor Create(IdClass: THMACClass);
function HashValue(const Input: string;
const key: string): System.TArray<System.Byte>;
LHMAC := GHMACRegistry[AAlgorithm].Create;
try
LHMAC.Key := ToBytes(AKey);
Result := TBytes(LHMAC.HashValue(ToBytes(AInput)));
finally
LHMAC.Free;
end;
function HMAC(const Algorithm: String; const Input, Key: string): TBytes;
begin
if not GHMACRegistry.ContainsKey(Algorithm) then
raise EMVCHMACException.CreateFmt('Unknown HMAC algorithm [%s]', [Algorithm]);
result := GHMACRegistry[Algorithm].HashValue(Input, Key);
end;
procedure RegisterHMACAlgorithm(const AAlgorithm: String; AClazz: THMACClass);
procedure RegisterHMACAlgorithm(const Algorithm: String; Impl: IHMAC);
begin
if GHMACRegistry.ContainsKey(AAlgorithm) then
if GHMACRegistry.ContainsKey(Algorithm) then
raise EMVCHMACException.Create('Algorithm already registered');
GHMACRegistry.Add(AAlgorithm, AClazz);
GHMACRegistry.Add(Algorithm, Impl);
end;
procedure UnRegisterHMACAlgorithm(const AAlgorithm: String);
procedure UnRegisterHMACAlgorithm(const Algorithm: String);
begin
GHMACRegistry.Remove(AAlgorithm);
GHMACRegistry.Remove(Algorithm);
end;
{ TIdHMACWrapper }
constructor TIdHMACWrapper.Create(IdClass: THMACClass);
begin
FClass := IdClass;
end;
function TIdHMACWrapper.HashValue(const Input,
key: string): System.TArray<System.Byte>;
var
lHMAC: TIdHMAC;
begin
Assert(IdSSLOpenSSL.LoadOpenSSLLibrary, 'HMAC requires OpenSSL libraries');
lHMAC := FClass.Create;
try
lHMAC.Key := ToBytes(Key, IndyTextEncoding_UTF8);
Result := TBytes(lHMAC.HashValue(ToBytes(Input, IndyTextEncoding_UTF8)));
finally
lHMAC.Free;
end;
end;
initialization
Assert(IdSSLOpenSSL.LoadOpenSSLLibrary, 'HMAC requires OpenSSL libraries');
GHMACRegistry := TDictionary<string, THMACClass>.Create;
// registering based on hash function
RegisterHMACAlgorithm('md5', TIdHMACMD5);
RegisterHMACAlgorithm('sha1', TIdHMACSHA1);
RegisterHMACAlgorithm('sha224', TIdHMACSHA224);
RegisterHMACAlgorithm('sha256', TIdHMACSHA256);
RegisterHMACAlgorithm('sha384', TIdHMACSHA384);
RegisterHMACAlgorithm('sha512', TIdHMACSHA512);
GHMACRegistry := TDictionary<string, IHMAC>.Create;
// the same using the JWT naming
RegisterHMACAlgorithm('HS256', TIdHMACSHA256);
RegisterHMACAlgorithm('HS384', TIdHMACSHA384);
RegisterHMACAlgorithm('HS512', TIdHMACSHA512);
//registering based on hash function
RegisterHMACAlgorithm('md5', TIdHMACWrapper.create(TIdHMACMD5));
RegisterHMACAlgorithm('sha1', TIdHMACWrapper.create(TIdHMACSHA1));
RegisterHMACAlgorithm('sha224', TIdHMACWrapper.create(TIdHMACSHA224));
RegisterHMACAlgorithm('sha256', TIdHMACWrapper.create(TIdHMACSHA256));
RegisterHMACAlgorithm('sha384', TIdHMACWrapper.create(TIdHMACSHA384));
RegisterHMACAlgorithm('sha512', TIdHMACWrapper.create(TIdHMACSHA512));
//the same using the JWT naming
RegisterHMACAlgorithm('HS256', TIdHMACWrapper.create(TIdHMACSHA256));
RegisterHMACAlgorithm('HS384', TIdHMACWrapper.create(TIdHMACSHA384));
RegisterHMACAlgorithm('HS512', TIdHMACWrapper.create(TIdHMACSHA512));
finalization

View File

@ -29,11 +29,8 @@ unit MVCFramework.JWT;
interface
uses
System.SysUtils,
System.DateUtils,
System.Generics.Collections,
MVCFramework.Commons,
MVCFramework.HMAC,
MVCFramework,
MVCFramework.TypesAliases,
MVCFramework.Patches;
@ -181,6 +178,7 @@ type
TJWTCustomClaims = class(TJWTDictionaryObject)
property Items; default;
function AsCustomData: TMVCCustomData;
end;
TJWT = class
@ -212,6 +210,12 @@ type
implementation
uses
System.SysUtils
, MVCFramework.Commons
, MVCFramework.HMAC
, System.DateUtils;
{ TJWTRegisteredClaims }
function TJWTRegisteredClaims.GetAudience: String;
@ -334,6 +338,13 @@ begin
Items[Index] := IntToStr(DateTimeToUnix(Value, False));
end;
{ TJWTCustomClaims }
function TJWTCustomClaims.AsCustomData: TMVCCustomData;
begin
Result := TMVCCustomData.Create(FClaims);
end;
{ TJWT }
function TJWT.CheckExpirationTime(Payload: TJSONObject;
@ -475,11 +486,11 @@ begin
lPayload.AddPair(lCustomClaimName, FCustomClaims[lCustomClaimName]);
end;
lHeaderEncoded := B64Encode(lHeader.ToJSON);
lPayloadEncoded := B64Encode(lPayload.ToJSON);
lHeaderEncoded := URLSafeB64encode(lHeader.ToString, False);
lPayloadEncoded := URLSafeB64encode(lPayload.ToString, False);
lToken := lHeaderEncoded + '.' + lPayloadEncoded;
lBytes := HMAC(HMACAlgorithm, lToken, FSecretKey);
lHash := B64Encode(lBytes);
lHash := URLSafeB64encode(lBytes, false);
Result := lToken + '.' + lHash;
finally
lPayload.Free;
@ -505,7 +516,7 @@ begin
Exit(False);
end;
lJHeader := TJSONObject.ParseJSONValue(B64Decode(lPieces[0])) as TJSONObject;
lJHeader := TJSONObject.ParseJSONValue(URLSafeB64Decode(lPieces[0])) as TJSONObject;
try
if not Assigned(lJHeader) then
begin
@ -513,7 +524,7 @@ begin
Exit(False);
end;
lJPayload := TJSONObject.ParseJSONValue(B64Decode(lPieces[1])) as TJSONObject;
lJPayload := TJSONObject.ParseJSONValue(URLSafeB64Decode(lPieces[1])) as TJSONObject;
try
if not Assigned(lJPayload) then
begin
@ -529,8 +540,9 @@ begin
lAlgName := lJAlg.Value;
Result := Token = lPieces[0] + '.' + lPieces[1] + '.' +
B64Encode(
HMAC(lAlgName, lPieces[0] + '.' + lPieces[1], FSecretKey)
URLSafeB64encode(
HMAC(lAlgName, lPieces[0] + '.' + lPieces[1], FSecretKey),
False
);
// if the token is correctly signed and has not been tampered,
@ -589,9 +601,9 @@ begin
raise EMVCJWTException.Create(lError);
lPieces := Token.Split(['.']);
lJHeader := TJSONObject.ParseJSONValue(B64Decode(lPieces[0])) as TJSONObject;
lJHeader := TJSONObject.ParseJSONValue(URLSafeB64Decode(lPieces[0])) as TJSONObject;
try
lJPayload := TJSONObject.ParseJSONValue(B64Decode(lPieces[1])) as TJSONObject;
lJPayload := TJSONObject.ParseJSONValue(URLSafeB64Decode(lPieces[1])) as TJSONObject;
try
// loading data from token into self
FHMACAlgorithm := lJHeader.Values['alg'].Value;

View File

@ -90,6 +90,7 @@ uses
type
TSessionData = TDictionary<string, string>;
TMVCCustomData = TSessionData;
TMVCBaseViewEngine = class;
TMVCViewEngineClass = class of TMVCBaseViewEngine;
@ -798,15 +799,9 @@ end;
function TMVCWebRequest.Body: string;
var
Encoding: TEncoding;
Buffer: TArray<Byte>;
I: Integer;
{$IFNDEF BERLINORBETTER}
BufferOut: TArray<Byte>;
{$ENDIF}
begin
{ TODO -oEzequiel -cRefactoring : Refactoring the method TMVCWebRequest.Body }
if (FBody = EmptyStr) then
@ -815,17 +810,21 @@ begin
try
{$IFDEF BERLINORBETTER}
FWebRequest.ReadTotalContent; //Otherwise ISAPI Raises "Empty JSON BODY"
if (FCharset = EmptyStr) then
begin
SetLength(Buffer, 10);
for I := 0 to 9 do
Buffer[I] := FWebRequest.RawContent[I];
TEncoding.GetBufferEncoding(Buffer, Encoding, TEncoding.Default);
SetLength(Buffer, 0);
TEncoding.GetBufferEncoding(FWebRequest.RawContent, Encoding, TEncoding.Default);
// SetLength(Buffer, 10);
// for I := 0 to 9 do
// Buffer[I] := FWebRequest.RawContent[I];
// TEncoding.GetBufferEncoding(Buffer, Encoding, TEncoding.Default);
// SetLength(Buffer, 0);
end
else
begin
Encoding := TEncoding.GetEncoding(FCharset);
end;
FBody := Encoding.GetString(FWebRequest.RawContent);
{$ELSE}
@ -834,23 +833,25 @@ begin
FWebRequest.ReadClient(Buffer[0], FWebRequest.ContentLength);
if (FCharset = EmptyStr) then
begin
SetLength(BufferOut, 10);
for I := 0 to 9 do
begin
BufferOut[I] := Buffer[I];
end;
TEncoding.GetBufferEncoding(BufferOut, Encoding, TEncoding.Default);
SetLength(BufferOut, 0);
TEncoding.GetBufferEncoding(Buffer, Encoding, TEncoding.Default);
// SetLength(BufferOut, 10);
// for I := 0 to 9 do
// begin
// BufferOut[I] := Buffer[I];
// end;
// TEncoding.GetBufferEncoding(BufferOut, Encoding, TEncoding.Default);
// SetLength(BufferOut, 0);
end
else
begin
Encoding := TEncoding.GetEncoding(FCharset);
end;
FBody := Encoding.GetString(Buffer);
{$ENDIF}
finally
if Assigned(Encoding) then
Encoding.Free;
Encoding.Free;
end;
end;
Result := FBody;

View File

@ -1,2 +1,2 @@
const
DMVCFRAMEWORK_VERSION = '3.0.0 hydrogen RC6';
DMVCFRAMEWORK_VERSION = '3.0.0 hydrogen RC7';