Skip to content

Commit 7da112e

Browse files
committed
Class API.TwoFA implemented for handling Two-Factor Authentication algorithm.
API Session Cookie implemented for both API.JSON-RPC and API.RESTful. Add the API_SESSION_COOKIE key into the Web.config Method API.ActiveDirectory.IsValidPassword implemented Method API.Utility.GetRandomMD5 implemented Method API.Utility.GetRandomSHA256 implemented
1 parent c840650 commit 7da112e

24 files changed

+1208
-791
lines changed

rls/API.Library.dll

4.5 KB
Binary file not shown.

rls/API.Library.dll.config

Lines changed: 246 additions & 244 deletions
Large diffs are not rendered by default.

rls/API.Library.pdb

10 KB
Binary file not shown.

src/API.Library/API.Library.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
<Compile Include="Entities\Performance.cs" />
118118
<Compile Include="Entities\Performance.ADO.cs" />
119119
<Compile Include="Entities\ReCAPTCHA.cs" />
120+
<Compile Include="Entities\TwoFA.cs" />
120121
<Compile Include="Entities\Utility.cs" />
121122
<Compile Include="Properties\AssemblyInfo.cs" />
122123
<Compile Include="Properties\Resources.Designer.cs">

src/API.Library/App.config

Lines changed: 246 additions & 244 deletions
Large diffs are not rendered by default.

src/API.Library/Entities/API.Common.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ public class Common
9595
/// HTTP POST Request
9696
/// </summary>
9797
internal string httpPOST = null;
98+
/// <summary>
99+
/// Session cookie
100+
/// </summary>
101+
public static string SessionCookieName = ConfigurationManager.AppSettings["API_SESSION_COOKIE"];
98102

99103
/// <summary>
100104
/// Authenticate the user in the context
@@ -236,13 +240,13 @@ public class Common
236240
if (string.IsNullOrEmpty(NetworkUsername))
237241
{
238242
Log.Instance.Fatal("Undefined Network Username");
239-
return null;
243+
return false;
240244
}
241245

242246
if (String.IsNullOrEmpty(API_AD_DOMAIN))
243247
{
244248
Log.Instance.Fatal("Undefined AD Domain");
245-
return null;
249+
return false;
246250
}
247251

248252
// Query AD
@@ -265,15 +269,15 @@ public class Common
265269
if (UserPrincipal == null)
266270
{
267271
Log.Instance.Fatal("Undefined User Principal against AD");
268-
return null;
272+
return false;
269273
}
270274
return true;
271275
}
272276
catch (Exception e)
273277
{
274278
Log.Instance.Fatal("Unable to connect/query AD");
275279
Log.Instance.Fatal(e);
276-
return null;
280+
return false;
277281
}
278282
}
279283

@@ -284,7 +288,7 @@ public class Common
284288
{
285289
// Override userPrincipal for security
286290
UserPrincipal = null;
287-
return true;
291+
return null;
288292
}
289293

290294
/// <summary>
@@ -316,17 +320,23 @@ public class Common
316320

317321
// Query AD and get the logged username
318322
UserPrincipal = API_UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, NetworkUsername);
323+
if (UserPrincipal == null)
324+
{
325+
Log.Instance.Fatal("Undefined User Principal against AD");
326+
return false;
327+
}
328+
319329
return true;
320330
}
321331
catch (Exception e)
322332
{
323333
Log.Instance.Fatal("Unable to connect/query AD");
324334
Log.Instance.Fatal(e);
325-
return null;
335+
return false;
326336
}
327337
}
328338

329-
return true;
339+
return null;
330340
}
331341

332342
/// <summary>

src/API.Library/Entities/API.JSONRPC.cs

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public class JSONRPC : Common, IHttpHandler, IRequiresSessionState
3838
/// Mask parametrs
3939
/// </summary>
4040
private static List<string> API_JSONRPC_MASK_PARAMETERS = (ConfigurationManager.AppSettings["API_JSONRPC_MASK_PARAMETERS"]).Split(',').ToList<string>();
41+
4142
#endregion
4243

4344
#region Methods
@@ -81,15 +82,30 @@ public void ProcessRequest(HttpContext context)
8182
ParseError(ref context, JSONRPC_Request.id, error);
8283
}
8384

84-
// Authenticate and append credentials
85-
if (Authenticate(ref context) == false)
85+
// Get Session Cookie
86+
HttpCookie sessionCookie = null;
87+
if (!String.IsNullOrEmpty(SessionCookieName))
8688
{
87-
JSONRPC_Error error = new JSONRPC_Error { code = -32002 };
88-
ParseError(ref context, JSONRPC_Request.id, error);
89+
sessionCookie = context.Request.Cookies[SessionCookieName];
90+
}
91+
92+
JSONRPC_Output result = null;
93+
94+
bool? isAuthenticated = Authenticate(ref context);
95+
switch (isAuthenticated)
96+
{
97+
case null: //Anonymous authentication
98+
result = GetResult(ref context, JSONRPC_Request, sessionCookie);
99+
break;
100+
case true: //Windows Authentication
101+
result = GetResult(ref context, JSONRPC_Request, null);
102+
break;
103+
case false: //Error
104+
JSONRPC_Error error = new JSONRPC_Error { code = -32002 };
105+
ParseError(ref context, JSONRPC_Request.id, error);
106+
break;
89107
}
90108

91-
// Get results from the relevant method with the params
92-
JSONRPC_Output result = GetResult(ref context, JSONRPC_Request);
93109
if (result == null)
94110
{
95111
JSONRPC_Error error = new JSONRPC_Error { code = -32603 };
@@ -102,6 +118,22 @@ public void ProcessRequest(HttpContext context)
102118
}
103119
else
104120
{
121+
// Set the Session Cookie if requested
122+
if (!String.IsNullOrEmpty(SessionCookieName) && result.sessionCookie != null && result.sessionCookie.Name.Equals(SessionCookieName))
123+
{
124+
// No expiry time allowed in the future
125+
if (result.sessionCookie.Expires > DateTime.Now)
126+
{
127+
result.sessionCookie.Expires = default;
128+
}
129+
130+
result.sessionCookie.Secure = true;
131+
result.sessionCookie.Domain = null;
132+
result.sessionCookie.HttpOnly = true;
133+
result.sessionCookie.SameSite = SameSiteMode.Strict;
134+
context.Response.Cookies.Add(result.sessionCookie);
135+
}
136+
105137
// Check if the result.data is already a JSON type casted as: new JRaw(data);
106138
var jsonRaw = result.data as JRaw;
107139
if (jsonRaw != null)
@@ -352,7 +384,7 @@ private static MethodInfo MapMethod(JSONRPC_Request JSONRPC_Request)
352384
/// </summary>
353385
/// <param name="JSONRPC_Request"></param>
354386
/// <returns></returns>
355-
private dynamic GetResult(ref HttpContext context, JSONRPC_Request JSONRPC_Request)
387+
private dynamic GetResult(ref HttpContext context, JSONRPC_Request JSONRPC_Request, HttpCookie sessionCookie = null)
356388
{
357389
// Set the API object
358390
JSONRPC_API apiRequest = new JSONRPC_API();
@@ -363,6 +395,7 @@ private dynamic GetResult(ref HttpContext context, JSONRPC_Request JSONRPC_Reque
363395
apiRequest.userAgent = Utility.GetUserAgent();
364396
apiRequest.httpGET = httpGET;
365397
apiRequest.httpPOST = httpPOST;
398+
apiRequest.sessionCookie = sessionCookie;
366399

367400
// Hide password from logs
368401
Log.Instance.Info("API Request: " + MaskParameters(Utility.JsonSerialize_IgnoreLoopingReference(apiRequest)));
@@ -455,6 +488,10 @@ public class JSONRPC_Output
455488
/// </summary>
456489
public dynamic error { get; set; }
457490

491+
/// <summary>
492+
/// Session Cookie
493+
/// </summary>
494+
public HttpCookie sessionCookie { get; set; }
458495
#endregion
459496
}
460497

@@ -522,6 +559,11 @@ public class JSONRPC_API
522559
/// POST request
523560
/// </summary>
524561
public string httpPOST { get; internal set; }
562+
563+
/// <summary>
564+
/// Session Cookie
565+
/// </summary>
566+
public HttpCookie sessionCookie { get; internal set; }
525567
#endregion
526568
}
527569

src/API.Library/Entities/API.Map.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ public static JSONRPC_API RESTful2JSONRPC_API(RESTful_API RestfulApi)
3030
ipAddress = RestfulApi.ipAddress,
3131
userAgent = RestfulApi.userAgent,
3232
httpGET = RestfulApi.httpGET,
33-
httpPOST = RestfulApi.httpPOST
33+
httpPOST = RestfulApi.httpPOST,
34+
sessionCookie = RestfulApi.sessionCookie
3435
};
3536
}
3637

@@ -52,7 +53,8 @@ public static RESTful_API JSONRPC2RESTful_API(JSONRPC_API JsonRpcApi)
5253
ipAddress = JsonRpcApi.ipAddress,
5354
userAgent = JsonRpcApi.userAgent,
5455
httpGET = JsonRpcApi.httpGET,
55-
httpPOST = JsonRpcApi.httpPOST
56+
httpPOST = JsonRpcApi.httpPOST,
57+
sessionCookie = JsonRpcApi.sessionCookie
5658
};
5759
}
5860

src/API.Library/Entities/API.RESTful.cs

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,24 +65,60 @@ public void ProcessRequest(HttpContext context)
6565
ParseError(ref context, HttpStatusCode.ServiceUnavailable, "System maintenance");
6666
}
6767

68-
// Authenticate and append credentials
69-
if (Authenticate(ref context) == false)
68+
// Get Session Cookie
69+
HttpCookie sessionCookie = null;
70+
if (!String.IsNullOrEmpty(SessionCookieName))
7071
{
71-
ParseError(ref context, HttpStatusCode.Unauthorized, "Invalid authentication");
72+
sessionCookie = context.Request.Cookies[SessionCookieName];
7273
}
7374

74-
// Get results from the relevant method with the params
75-
RESTful_Output result = GetResult(ref context);
75+
RESTful_Output result = null;
76+
77+
bool? isAuthenticated = Authenticate(ref context);
78+
switch (isAuthenticated)
79+
{
80+
case null: //Anonymous authentication
81+
result = GetResult(ref context, sessionCookie);
82+
break;
83+
case true: //Windows Authentication
84+
result = GetResult(ref context);
85+
86+
break;
87+
case false: //Error
88+
ParseError(ref context, HttpStatusCode.InternalServerError, "Internal Error");
89+
break;
90+
}
91+
92+
7693

7794
if (result == null)
7895
{
7996
ParseError(ref context, HttpStatusCode.InternalServerError, "Internal Error");
8097
}
98+
99+
81100
else if (result.statusCode == HttpStatusCode.OK)
82101
{
83102
context.Response.StatusCode = (int)result.statusCode;
84103
context.Response.ContentType = result.mimeType;
85104

105+
// Set the Session Cookie if requested
106+
if (!String.IsNullOrEmpty(SessionCookieName) && result.sessionCookie != null && result.sessionCookie.Name.Equals(SessionCookieName))
107+
{
108+
// No expiry time allowed in the future
109+
if (result.sessionCookie.Expires > DateTime.Now)
110+
{
111+
result.sessionCookie.Expires = default;
112+
}
113+
114+
result.sessionCookie.Secure = true;
115+
result.sessionCookie.Domain = null;
116+
result.sessionCookie.HttpOnly = true;
117+
result.sessionCookie.SameSite = SameSiteMode.Strict;
118+
context.Response.Cookies.Add(result.sessionCookie);
119+
}
120+
121+
86122
if (!String.IsNullOrEmpty(result.fileName))
87123
{
88124
context.Response.AppendHeader("Content-Disposition", new ContentDisposition { Inline = true, FileName = result.fileName }.ToString());
@@ -276,7 +312,7 @@ private static MethodInfo MapMethod(List<string> requestParams)
276312
/// Invoke and return the results from the mapped method
277313
/// </summary>
278314
/// <returns></returns>
279-
private dynamic GetResult(ref HttpContext context)
315+
private dynamic GetResult(ref HttpContext context, HttpCookie sessionCookie = null)
280316
{
281317
// Set the API object
282318
RESTful_API apiRequest = new RESTful_API();
@@ -287,6 +323,7 @@ private dynamic GetResult(ref HttpContext context)
287323
apiRequest.userAgent = Utility.GetUserAgent();
288324
apiRequest.httpGET = httpGET;
289325
apiRequest.httpPOST = httpPOST;
326+
apiRequest.sessionCookie = sessionCookie;
290327

291328
// Hide password from logs
292329
Log.Instance.Info("API Request: " + Utility.JsonSerialize_IgnoreLoopingReference(apiRequest));
@@ -335,6 +372,11 @@ public class RESTful_Output
335372
/// RESTful filename (optional)
336373
/// </summary>
337374
public string fileName { get; set; }
375+
376+
/// <summary>
377+
/// Session Cookie
378+
/// </summary>
379+
public HttpCookie sessionCookie { get; set; }
338380
#endregion
339381
}
340382

@@ -379,6 +421,10 @@ public class RESTful_API
379421
/// </summary>
380422
public string httpPOST { get; internal set; }
381423

424+
/// <summary>
425+
/// Session Cookie
426+
/// </summary>
427+
public HttpCookie sessionCookie { get; internal set; }
382428
#endregion
383429
}
384430
}

src/API.Library/Entities/ActiveDirectory.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
2-
using System.DirectoryServices.AccountManagement;
32
using System.Collections.Generic;
43
using System.Configuration;
4+
using System.DirectoryServices.AccountManagement;
55
using System.Dynamic;
66
using System.Linq;
77

@@ -146,8 +146,25 @@ public static bool IsAuthenticated(dynamic userPrincipal)
146146
else
147147
return true;
148148
}
149+
150+
/// <summary>
151+
/// Validate a Password against an AD account
152+
/// </summary>
153+
/// <param name="userPrincipal"></param>
154+
/// <param name="password"></param>
155+
/// <returns></returns>
156+
public static bool IsPasswordValid(dynamic userPrincipal, string password)
157+
{
158+
bool isValid = false;
159+
using (PrincipalContext pricipalContext = new PrincipalContext(ContextType.Domain, adDomain, String.IsNullOrEmpty(adPath) ? null : adPath, String.IsNullOrEmpty(adUsername) ? null : adUsername, String.IsNullOrEmpty(adPassword) ? null : adPassword))
160+
{
161+
// validate the credentials
162+
isValid = pricipalContext.ValidateCredentials(userPrincipal, password);
163+
}
164+
return isValid;
165+
}
149166
#endregion
150167

151168
}
152169

153-
}
170+
}

0 commit comments

Comments
 (0)