Skip to content

Commit 37e85fd

Browse files
authored
Feature/stability (#226)
* cxone crawl stability * fix issue #225 * scan stat record stabilization * documentation updates * enable odata api
1 parent bf4afe3 commit 37e85fd

File tree

13 files changed

+572
-139
lines changed

13 files changed

+572
-139
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Configuration;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace CxAnalytix.Configuration.Impls
9+
{
10+
public sealed class CxSASTAPIOverrides : ConfigurationElement
11+
{
12+
13+
[ConfigurationProperty("Project", IsRequired = false, DefaultValue = false)]
14+
public bool Project
15+
{
16+
get => (bool)this["Project"];
17+
set => this["Project"] = value;
18+
}
19+
}
20+
}

Libs/Configuration/Impls/CxSASTConnection.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,11 @@ public String MNOUrl
1414
set { this["mnoURL"] = value; }
1515
}
1616

17+
[ConfigurationProperty("UseOdata", IsRequired = false)]
18+
public CxSASTAPIOverrides Overrides
19+
{
20+
get => (CxSASTAPIOverrides)this["UseOdata"];
21+
set => this["UseOdata"] = value;
22+
}
1723
}
1824
}

Libs/CxRestClient/SAST/CxProjects.cs

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ public class CxProjects
1818
{
1919
private static ILog _log = LogManager.GetLogger(typeof(CxProjects));
2020

21-
private static String URL_SUFFIX = "cxrestapi/projects";
21+
private static readonly String REST_URL_SUFFIX = "cxrestapi/projects";
22+
23+
private static readonly int ODATA_TOP = 25;
24+
private static readonly String ODATA_URL_SUFFIX = "cxwebinterface/odata/v1/Projects?$expand=CustomFields&$select=OwningTeamId,PresetId,Id,Name,IsPublic" +
25+
$"&$orderby=Id asc&$top={ODATA_TOP}";
2226

2327
private static String _apiVersion = null;
2428

@@ -41,29 +45,94 @@ private static String GetApiVersion(CxSASTRestContext ctx, CancellationToken tok
4145
private CxProjects()
4246
{ }
4347

48+
#region DTOs
49+
4450
[JsonObject(MemberSerialization.OptIn)]
4551
public class ProjectCustomFields
4652
{
4753
[JsonProperty(PropertyName = "name")]
4854
public String FieldName { get; internal set; }
55+
56+
[JsonProperty(PropertyName = "FieldName")]
57+
private String odata_FieldName
58+
{
59+
get => FieldName;
60+
set => FieldName = value;
61+
}
62+
63+
4964
[JsonProperty(PropertyName = "value")]
5065
public String FieldValue { get; internal set; }
66+
67+
[JsonProperty(PropertyName = "FieldValue")]
68+
private String odata_FieldValue
69+
{
70+
get => FieldValue;
71+
set => FieldValue = value;
72+
}
73+
5174
}
5275

5376
[JsonObject(MemberSerialization.OptIn)]
5477
public class Project
5578
{
5679
[JsonProperty(PropertyName = "teamId")]
5780
public String TeamId { get; internal set; }
81+
[JsonProperty(PropertyName = "OwningTeamId")]
82+
private String odata_TeamId
83+
{
84+
get => TeamId;
85+
set => TeamId = value;
86+
}
87+
88+
89+
5890
public int PresetId { get; internal set; }
91+
92+
5993
[JsonProperty(PropertyName = "id")]
6094
public int ProjectId { get; internal set; }
95+
[JsonProperty(PropertyName = "Id")]
96+
private int odata_ProjectId
97+
{
98+
get => ProjectId;
99+
set => ProjectId = value;
100+
}
101+
102+
61103
[JsonProperty(PropertyName = "name")]
62104
public String ProjectName { get; internal set; }
105+
[JsonProperty(PropertyName = "Name")]
106+
private String odata_ProjectName
107+
{
108+
get => ProjectName;
109+
set => ProjectName = value;
110+
}
111+
112+
113+
114+
63115
[JsonProperty(PropertyName = "isPublic")]
64116
public bool IsPublic { get; internal set; }
117+
[JsonProperty(PropertyName = "IsPublic")]
118+
private bool odata_IsPublic
119+
{
120+
get => IsPublic;
121+
set => IsPublic = value;
122+
}
123+
124+
125+
65126
[JsonProperty(PropertyName = "customFields")]
66127
public List<ProjectCustomFields> CustomFields { get; internal set; }
128+
[JsonProperty(PropertyName = "CustomFields")]
129+
private List<ProjectCustomFields> odata_CustomFields
130+
{
131+
get => CustomFields;
132+
set => CustomFields = value;
133+
}
134+
135+
67136

68137
[JsonProperty(PropertyName = "isBranched")]
69138
public bool IsBranched { get; internal set; }
@@ -78,6 +147,7 @@ public class Project
78147
public override string ToString() =>
79148
$"{ProjectId}:{ProjectName} [TeamId: {TeamId} Public: {IsPublic} CustomFields: {CustomFields.Count}]";
80149
}
150+
#endregion
81151

82152
private class ProjectReader : IEnumerable<Project>, IEnumerator<Project>, IDisposable
83153
{
@@ -161,12 +231,57 @@ public void Reset()
161231
{
162232
throw new NotImplementedException();
163233
}
234+
}
164235

236+
private static IEnumerable<Project> GetProjects_odata(CxSASTRestContext ctx, CancellationToken token)
237+
{
238+
List<Project> returnedResults = new();
239+
240+
var filter = new Dictionary<String, String>();
241+
List<Project> fetchedPage = null;
242+
243+
do
244+
{
245+
String requestUrl = UrlUtils.MakeUrl(ctx.Sast.ApiUrl, ODATA_URL_SUFFIX, filter);
165246

247+
using (var projectReader = WebOperation.ExecuteGet<ProjectReader>(
248+
ctx.Sast.Json.CreateClient
249+
, (response) =>
250+
{
251+
using (var sr = new StreamReader(response.Content.ReadAsStreamAsync().Result))
252+
using (var jtr = new JsonTextReader(sr))
253+
{
254+
JToken jt = JToken.Load(jtr);
255+
256+
return new ProjectReader(jt["value"], ctx, token);
257+
}
258+
}
259+
, requestUrl
260+
, ctx.Sast
261+
, token))
262+
fetchedPage = new List<Project>(projectReader);
263+
264+
if (fetchedPage != null)
265+
{
266+
returnedResults.AddRange(fetchedPage);
267+
filter["$filter"] = $"id gt {fetchedPage[fetchedPage.Count - 1].ProjectId}";
268+
}
269+
270+
271+
} while (fetchedPage != null && fetchedPage.Count == ODATA_TOP);
272+
273+
return returnedResults;
166274
}
167275

276+
public static IEnumerable<Project> GetProjects(CxSASTRestContext ctx, CancellationToken token, bool useOData)
277+
{
278+
if (useOData)
279+
return GetProjects_odata(ctx, token);
280+
else
281+
return GetProjects_rest(ctx, token);
282+
}
168283

169-
public static IEnumerable<Project> GetProjects(CxSASTRestContext ctx, CancellationToken token)
284+
private static IEnumerable<Project> GetProjects_rest(CxSASTRestContext ctx, CancellationToken token)
170285
{
171286
using (var projectReader = WebOperation.ExecuteGet<ProjectReader>(
172287
ctx.Sast.Json.CreateClient
@@ -180,7 +295,7 @@ public static IEnumerable<Project> GetProjects(CxSASTRestContext ctx, Cancellati
180295
return new ProjectReader(jt, ctx, token);
181296
}
182297
}
183-
, UrlUtils.MakeUrl(ctx.Sast.ApiUrl, URL_SUFFIX)
298+
, UrlUtils.MakeUrl(ctx.Sast.ApiUrl, REST_URL_SUFFIX)
184299
, ctx.Sast
185300
, token, apiVersion: GetApiVersion(ctx, token) ))
186301
return new List<Project>(projectReader);

Libs/CxRestClient/SAST/CxScanStatistics.cs

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using CxAnalytix.Exceptions;
2+
using CxAnalytix.Extensions;
23
using CxRestClient.Utility;
34
using log4net;
45
using Newtonsoft.Json;
@@ -7,6 +8,7 @@
78
using System.Collections.Generic;
89
using System.IO;
910
using System.Linq;
11+
using System.Linq.Expressions;
1012
using System.Net;
1113
using System.Net.Http;
1214
using System.Text;
@@ -270,24 +272,51 @@ internal FullScanStatistics()
270272

271273
public static FullScanStatistics GetScanFullStatistics(CxSASTRestContext ctx, CancellationToken token, String scanId)
272274
{
273-
var statistics = GetScanStatistics(ctx, token, scanId);
274-
275-
var pf = GetScanParsedFiles(ctx, token, scanId);
276-
var fq = GetScanFailedQueries(ctx, token, scanId);
277-
var fgq = GetScanFailedGeneralQueries(ctx, token, scanId);
278-
var sgq = GetScanSuccessfulGeneralQueries(ctx, token, scanId);
279-
280-
if (statistics.Result == null)
281-
return null;
282-
283-
return new FullScanStatistics()
284-
{
285-
Statistics = statistics.Result,
286-
ParsedFiles = pf.Result,
287-
FailedQueries = fq.Result,
288-
FailedGeneralQueries = fgq.Result,
289-
SuccessGeneralQueries = sgq.Result
290-
};
275+
CancellationTokenSource localToken = new();
276+
277+
List<Task> runningTasks = new();
278+
279+
using (token.Register(() => localToken.Cancel()))
280+
try
281+
{
282+
var statistics = GetScanStatistics(ctx, localToken.Token, scanId);
283+
runningTasks.Add(statistics);
284+
285+
var pf = GetScanParsedFiles(ctx, localToken.Token, scanId);
286+
runningTasks.Add(pf);
287+
288+
var fq = GetScanFailedQueries(ctx, localToken.Token, scanId);
289+
runningTasks.Add(fq);
290+
291+
var fgq = GetScanFailedGeneralQueries(ctx, localToken.Token, scanId);
292+
runningTasks.Add(fgq);
293+
294+
var sgq = GetScanSuccessfulGeneralQueries(ctx, localToken.Token, scanId);
295+
runningTasks.Add(sgq);
296+
297+
298+
if (statistics.Result != null)
299+
return new FullScanStatistics()
300+
{
301+
Statistics = statistics.Result,
302+
ParsedFiles = pf.Result,
303+
FailedQueries = fq.Result,
304+
FailedGeneralQueries = fgq.Result,
305+
SuccessGeneralQueries = sgq.Result
306+
};
307+
}
308+
catch (Exception)
309+
{
310+
localToken.Cancel();
311+
throw;
312+
}
313+
finally
314+
{
315+
runningTasks.SafeWaitAllToEnd();
316+
runningTasks.DisposeTasks();
317+
}
318+
319+
return null;
291320
}
292321

293322
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace CxAnalytix.Exceptions
8+
{
9+
public class ScanCrawlException : Exception
10+
{
11+
private static String MakeMessage(String scanId, String projectName, String teamName) =>
12+
$"Exception caught processing scan [{scanId}] in [{projectName}]" +
13+
(!String.IsNullOrEmpty(teamName) ? $" assigned to team(s) [{teamName}]" : "");
14+
15+
16+
public ScanCrawlException (String scanId, String projectName, String teamName)
17+
: base(ScanCrawlException.MakeMessage(scanId, projectName, teamName) )
18+
{
19+
20+
}
21+
public ScanCrawlException(String scanId, String projectName, String teamName, Exception ex)
22+
: base(ScanCrawlException.MakeMessage(scanId, projectName, teamName), ex)
23+
{
24+
25+
}
26+
}
27+
}

Libs/Executive/ExecuteLoop.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace CxAnalytix.Executive
1111
{
1212
public class ExecuteLoop : ExecuteOnce
1313
{
14-
private static readonly ILog appLog = LogManager.GetLogger(typeof(ExecuteLoop));
14+
private static readonly ILog _log = LogManager.GetLogger(typeof(ExecuteLoop));
1515

1616
public static new void Execute(CancellationTokenSource t)
1717
{
@@ -35,19 +35,23 @@ public class ExecuteLoop : ExecuteOnce
3535
}
3636
catch (Exception ex)
3737
{
38-
appLog.Error("Vulnerability data transformation aborted due to unhandled exception.", ex);
38+
_log.Error("Vulnerability data transformation aborted due to unhandled exception.", ex);
3939
}
4040

4141

4242
GC.Collect();
4343

44-
Task.Delay(Service.ProcessPeriodMinutes * 60 * 1000, t.Token).Wait();
44+
using (var delay = Task.Delay(Service.ProcessPeriodMinutes * 60 * 1000, t.Token))
45+
delay.Wait(t.Token);
46+
4547
} while (!t.Token.IsCancellationRequested);
4648

49+
_log.Info("Execution complete, ending.");
50+
4751
}
4852
private static void Fatal(Exception ex, CancellationTokenSource ct)
4953
{
50-
appLog.Error("Fatal exception caught, program ending.", ex);
54+
_log.Error("Fatal exception caught, program ending.", ex);
5155
ct.Cancel();
5256
Process.GetCurrentProcess().Kill(true);
5357

0 commit comments

Comments
 (0)