-
Notifications
You must be signed in to change notification settings - Fork 0
/
Sx.DDD.Infra.Auth.Rest.pas
276 lines (230 loc) · 9.36 KB
/
Sx.DDD.Infra.Auth.Rest.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
unit Sx.DDD.Infra.Auth.Rest;
{$mode delphi}
interface
uses
SysUtils,
Classes,
mormot.core.base,
mormot.orm.base,
mormot.orm.core,
mormot.rest.core,
Sx.DDD.Core,
Sx.DDD.Auth.Interfaces;
{ ----- Authentication Implementation using SHA-256 dual step challenge }
type
/// ORM object to persist authentication information, i.e. TAuthInfo
TOrmUserAuth = class(TOrm)
protected
fLogon: RawUTF8;
fHashedPassword: RawUTF8;
class procedure InternalDefineModel(Props: TOrmProperties); override;
published
/// will map TAuthInfo.LogonName
// - is defined as "stored AS_UNIQUE" so that it may be used as primary key
property Logon: RawUTF8 read fLogon write fLogon stored AS_UNIQUE;
/// the password, stored in a hashed form
// - this property does not exist at TAuthInfo level, so will be private
// to the storage layer - which is the safest option possible
property HashedPassword: RawUTF8 read fHashedPassword write fHashedPassword;
end;
/// generic class for implementing authentication
// - do not instantiate this abstract class, but e.g. TDDDAuthenticationSHA256
// or TDDDAuthenticationMD5
TDDDAuthenticationAbstract = class(TDDDRepositoryRestCommand,IDomAuthCommand)
protected
fChallengeLogonName: RawUTF8;
fChallengeNonce: TAuthQueryNonce;
fLogged: boolean;
// inherited classes should override this method with the proper algorithm
class function DoHash(const aValue: TAuthQueryNonce): TAuthQueryNonce; virtual; abstract;
public
/// initiate the first phase of a dual pass challenge authentication
function ChallengeSelectFirst(const aLogonName: RawUTF8): TAuthQueryNonce;
/// validate the first phase of a dual pass challenge authentication
function ChallengeSelectFinal(const aChallengedPassword: TAuthQueryNonce): TCQRSResult;
/// set the credential for Get() or further IDomAuthCommand.Update/Delete
// - this method execution will be disabled for most clients
function SelectByName(const aLogonName: RawUTF8): TCQRSResult;
/// returns TRUE if the dual pass challenge did succeed
function Logged: boolean;
/// returns the logon name of the authenticated user
function LogonName: RawUTF8;
/// retrieve some information about the current selected credential
function Get(out aAggregate: TAuthInfo): TCQRSResult;
/// register a new credential, from its LogonName/HashedPassword values
// - on success, the newly created credential will be the currently selected
function Add(const aLogonName: RawUTF8; aHashedPassword: TAuthQueryNonce): TCQRSResult;
/// update the current selected credential password
function UpdatePassword(const aHashedPassword: TAuthQueryNonce): TCQRSResult;
/// class method to be used to compute a password hash from its plain value
class function ComputeHashPassword(const aLogonName,aPassword: RawUTF8): TAuthQueryNonce;
/// class method to be used on the client side to resolve the challenge
// - is basically
// ! result := DoHash(aLogonName+':'+aChallengeFromServer+':'+
// ! ComputeHashPassword(aLogonName,aPlainPassword));
class function ClientComputeChallengedPassword(
const aLogonName,aPlainPassword: RawUTF8;
const aChallengeFromServer: TAuthQueryNonce): TAuthQueryNonce; virtual;
end;
/// allows to specify which actual hashing algorithm would be used
// - i.e. either TDDDAuthenticationSHA256 or TDDDAuthenticationMD5
TDDDAuthenticationClass = class of TDDDAuthenticationAbstract;
/// implements authentication using SHA-256 hashing
// - more secure than TDDDAuthenticationMD5
TDDDAuthenticationSHA256 = class(TDDDAuthenticationAbstract)
protected
/// will use SHA-256 algorithm for hashing, and the class name as salt
class function DoHash(const aValue: TAuthQueryNonce): TAuthQueryNonce; override;
end;
/// implements authentication using MD5 hashing
// - less secure than TDDDAuthenticationSHA256
TDDDAuthenticationMD5 = class(TDDDAuthenticationAbstract)
protected
/// will use MD5 algorithm for hashing, and the class name as salt
class function DoHash(const aValue: TAuthQueryNonce): TAuthQueryNonce; override;
end;
/// abstract factory of IDomAuthCommand repository instances using REST
TDDDAuthenticationRestFactoryAbstract = class(TDDDRepositoryRestFactory)
protected
public
/// initialize a factory with the supplied implementation algorithm
constructor Create(aRest: TRest; aImplementationClass: TDDDAuthenticationClass;
aOwner: TDDDRepositoryRestManager); reintroduce;
end;
/// factory of IDomAuthCommand repository instances using a RESTful ORM access
// and SHA-256 hashing algorithm
TDDDAuthenticationRestFactorySHA256 = class(TDDDAuthenticationRestFactoryAbstract)
protected
public
/// initialize a factory with the SHA-256 implementation algorithm
constructor Create(aRest: TRest; aOwner: TDDDRepositoryRestManager=nil); reintroduce;
end;
/// factory of IDomAuthCommand repository instances using a RESTful ORM access
// and SHA-256 hashing algorithm
TDDDAuthenticationRestFactoryMD5 = class(TDDDAuthenticationRestFactoryAbstract)
protected
public
/// initialize a factory with the SHA-256 implementation algorithm
constructor Create(aRest: TRest; aOwner: TDDDRepositoryRestManager=nil); reintroduce;
end;
implementation
uses
mormot.core.datetime,
mormot.crypt.core;
{ TDDDAuthenticationAbstract }
function TDDDAuthenticationAbstract.ChallengeSelectFirst(
const aLogonName: RawUTF8): TAuthQueryNonce;
begin
fLogged := false;
fChallengeLogonName := Trim(aLogonName);
fChallengeNonce := DoHash(aLogonName+NowToString);
result := fChallengeNonce;
end;
function TDDDAuthenticationAbstract.ChallengeSelectFinal(
const aChallengedPassword: TAuthQueryNonce): TCQRSResult;
begin
if (fChallengeLogonName='') or (fChallengeNonce='') then
result := CqrsSetResultError(cqrsBadRequest) else
result := SelectByName(fChallengeLogonName);
if result<>cqrsSuccess then
exit;
CqrsBeginMethod(qaNone, result);
if DoHash(fChallengeLogonName+':'+fChallengeNonce+':'+
(fCurrentORMInstance as TOrmUserAuth).HashedPassword)=aChallengedPassword then begin
fLogged := true;
CqrsSetResult(cqrsSuccess,result);
end else
CqrsSetResultMsg(cqrsBadRequest,'Wrong Password for [%]',[fChallengeLogonName],result);
fChallengeNonce := '';
fChallengeLogonName := '';
end;
function TDDDAuthenticationAbstract.LogonName: RawUTF8;
begin
if (fCurrentORMInstance=nil) or not Logged then
result := '' else
result := TOrmUserAuth(fCurrentORMInstance).Logon;
end;
function TDDDAuthenticationAbstract.Logged: boolean;
begin
result := fLogged;
end;
class function TDDDAuthenticationAbstract.ComputeHashPassword(
const aLogonName, aPassword: RawUTF8): TAuthQueryNonce;
begin
result := DoHash(aLogonName+':'+aPassword);
end;
class function TDDDAuthenticationAbstract.ClientComputeChallengedPassword(
const aLogonName,aPlainPassword: RawUTF8; const aChallengeFromServer: TAuthQueryNonce): TAuthQueryNonce;
begin // see TDDDAuthenticationAbstract.ChallengeSelectFinal
result := DoHash(aLogonName+':'+aChallengeFromServer+':'+
ComputeHashPassword(aLogonName,aPlainPassword));
end;
function TDDDAuthenticationAbstract.SelectByName(
const aLogonName: RawUTF8): TCQRSResult;
begin
result := ORMSelectOne('Logon=?',[aLogonName],(aLogonName=''));
end;
function TDDDAuthenticationAbstract.Get(
out aAggregate: TAuthInfo): TCQRSResult;
begin
result := ORMGetAggregate(aAggregate);
end;
function TDDDAuthenticationAbstract.Add(const aLogonName: RawUTF8;
aHashedPassword: TAuthQueryNonce): TCQRSResult;
begin
if not CqrsBeginMethod(qaCommandDirect,result) then
exit;
with fCurrentORMInstance as TOrmUserAuth do begin
Logon := aLogonName;
HashedPassword := aHashedPassword;
end;
ORMPrepareForCommit(ooInsert,nil,result);
end;
function TDDDAuthenticationAbstract.UpdatePassword(
const aHashedPassword: TAuthQueryNonce): TCQRSResult;
begin
if not CqrsBeginMethod(qaCommandOnSelect,result) then
exit;
(fCurrentORMInstance as TOrmUserAuth).HashedPassword := aHashedPassword;
ORMPrepareForCommit(ooUpdate,nil,result);
end;
{ TDDDAuthenticationSHA256 }
class function TDDDAuthenticationSHA256.DoHash(
const aValue: TAuthQueryNonce): TAuthQueryNonce;
begin
result := SHA256(RawUTF8(ClassName)+aValue);
end;
{ TDDDAuthenticationMD5 }
class function TDDDAuthenticationMD5.DoHash(
const aValue: TAuthQueryNonce): TAuthQueryNonce;
begin
result := MD5(RawUTF8(ClassName)+aValue);
end;
{ TDDDAuthenticationRestFactoryAbstract }
constructor TDDDAuthenticationRestFactoryAbstract.Create(aRest: TRest;
aImplementationClass: TDDDAuthenticationClass;
aOwner: TDDDRepositoryRestManager);
begin
inherited Create(
IDomAuthCommand,aImplementationClass,TAuthInfo,aRest,TOrmUserAuth,
['Logon','LogonName'],aOwner);
end;
{ TDDDAuthenticationRestFactorySHA256 }
constructor TDDDAuthenticationRestFactorySHA256.Create(aRest: TRest;
aOwner: TDDDRepositoryRestManager);
begin
inherited Create(aRest,TDDDAuthenticationSHA256,aOwner);
end;
{ TDDDAuthenticationRestFactoryMD5 }
constructor TDDDAuthenticationRestFactoryMD5.Create(aRest: TRest;
aOwner: TDDDRepositoryRestManager);
begin
inherited Create(aRest,TDDDAuthenticationMD5,aOwner);
end;
{ TOrmUserAuth }
class procedure TOrmUserAuth.InternalDefineModel(
Props: TOrmProperties);
begin
AddFilterNotVoidText(['Logon','HashedPassword']);
end;
end.