diff --git a/Libs/Configuration/Impls/CxSASTAPIOverrides.cs b/Libs/Configuration/Impls/CxSASTAPIOverrides.cs new file mode 100644 index 00000000..583818b5 --- /dev/null +++ b/Libs/Configuration/Impls/CxSASTAPIOverrides.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CxAnalytix.Configuration.Impls +{ + public sealed class CxSASTAPIOverrides : ConfigurationElement + { + + [ConfigurationProperty("Project", IsRequired = false, DefaultValue = false)] + public bool Project + { + get => (bool)this["Project"]; + set => this["Project"] = value; + } + } +} diff --git a/Libs/Configuration/Impls/CxSASTConnection.cs b/Libs/Configuration/Impls/CxSASTConnection.cs index 6bbc1a53..5706b2a8 100644 --- a/Libs/Configuration/Impls/CxSASTConnection.cs +++ b/Libs/Configuration/Impls/CxSASTConnection.cs @@ -14,5 +14,11 @@ public String MNOUrl set { this["mnoURL"] = value; } } + [ConfigurationProperty("UseOdata", IsRequired = false)] + public CxSASTAPIOverrides Overrides + { + get => (CxSASTAPIOverrides)this["UseOdata"]; + set => this["UseOdata"] = value; + } } } diff --git a/Libs/CxRestClient/SAST/CxProjects.cs b/Libs/CxRestClient/SAST/CxProjects.cs index a863692c..b6ab0866 100644 --- a/Libs/CxRestClient/SAST/CxProjects.cs +++ b/Libs/CxRestClient/SAST/CxProjects.cs @@ -18,7 +18,11 @@ public class CxProjects { private static ILog _log = LogManager.GetLogger(typeof(CxProjects)); - private static String URL_SUFFIX = "cxrestapi/projects"; + private static readonly String REST_URL_SUFFIX = "cxrestapi/projects"; + + private static readonly int ODATA_TOP = 25; + private static readonly String ODATA_URL_SUFFIX = "cxwebinterface/odata/v1/Projects?$expand=CustomFields&$select=OwningTeamId,PresetId,Id,Name,IsPublic" + + $"&$orderby=Id asc&$top={ODATA_TOP}"; private static String _apiVersion = null; @@ -41,13 +45,32 @@ private static String GetApiVersion(CxSASTRestContext ctx, CancellationToken tok private CxProjects() { } + #region DTOs + [JsonObject(MemberSerialization.OptIn)] public class ProjectCustomFields { [JsonProperty(PropertyName = "name")] public String FieldName { get; internal set; } + + [JsonProperty(PropertyName = "FieldName")] + private String odata_FieldName + { + get => FieldName; + set => FieldName = value; + } + + [JsonProperty(PropertyName = "value")] public String FieldValue { get; internal set; } + + [JsonProperty(PropertyName = "FieldValue")] + private String odata_FieldValue + { + get => FieldValue; + set => FieldValue = value; + } + } [JsonObject(MemberSerialization.OptIn)] @@ -55,15 +78,61 @@ public class Project { [JsonProperty(PropertyName = "teamId")] public String TeamId { get; internal set; } + [JsonProperty(PropertyName = "OwningTeamId")] + private String odata_TeamId + { + get => TeamId; + set => TeamId = value; + } + + + public int PresetId { get; internal set; } + + [JsonProperty(PropertyName = "id")] public int ProjectId { get; internal set; } + [JsonProperty(PropertyName = "Id")] + private int odata_ProjectId + { + get => ProjectId; + set => ProjectId = value; + } + + [JsonProperty(PropertyName = "name")] public String ProjectName { get; internal set; } + [JsonProperty(PropertyName = "Name")] + private String odata_ProjectName + { + get => ProjectName; + set => ProjectName = value; + } + + + + [JsonProperty(PropertyName = "isPublic")] public bool IsPublic { get; internal set; } + [JsonProperty(PropertyName = "IsPublic")] + private bool odata_IsPublic + { + get => IsPublic; + set => IsPublic = value; + } + + + [JsonProperty(PropertyName = "customFields")] public List CustomFields { get; internal set; } + [JsonProperty(PropertyName = "CustomFields")] + private List odata_CustomFields + { + get => CustomFields; + set => CustomFields = value; + } + + [JsonProperty(PropertyName = "isBranched")] public bool IsBranched { get; internal set; } @@ -78,6 +147,7 @@ public class Project public override string ToString() => $"{ProjectId}:{ProjectName} [TeamId: {TeamId} Public: {IsPublic} CustomFields: {CustomFields.Count}]"; } + #endregion private class ProjectReader : IEnumerable, IEnumerator, IDisposable { @@ -161,12 +231,57 @@ public void Reset() { throw new NotImplementedException(); } + } + private static IEnumerable GetProjects_odata(CxSASTRestContext ctx, CancellationToken token) + { + List returnedResults = new(); + + var filter = new Dictionary(); + List fetchedPage = null; + + do + { + String requestUrl = UrlUtils.MakeUrl(ctx.Sast.ApiUrl, ODATA_URL_SUFFIX, filter); + using (var projectReader = WebOperation.ExecuteGet( + ctx.Sast.Json.CreateClient + , (response) => + { + using (var sr = new StreamReader(response.Content.ReadAsStreamAsync().Result)) + using (var jtr = new JsonTextReader(sr)) + { + JToken jt = JToken.Load(jtr); + + return new ProjectReader(jt["value"], ctx, token); + } + } + , requestUrl + , ctx.Sast + , token)) + fetchedPage = new List(projectReader); + + if (fetchedPage != null) + { + returnedResults.AddRange(fetchedPage); + filter["$filter"] = $"id gt {fetchedPage[fetchedPage.Count - 1].ProjectId}"; + } + + + } while (fetchedPage != null && fetchedPage.Count == ODATA_TOP); + + return returnedResults; } + public static IEnumerable GetProjects(CxSASTRestContext ctx, CancellationToken token, bool useOData) + { + if (useOData) + return GetProjects_odata(ctx, token); + else + return GetProjects_rest(ctx, token); + } - public static IEnumerable GetProjects(CxSASTRestContext ctx, CancellationToken token) + private static IEnumerable GetProjects_rest(CxSASTRestContext ctx, CancellationToken token) { using (var projectReader = WebOperation.ExecuteGet( ctx.Sast.Json.CreateClient @@ -180,7 +295,7 @@ public static IEnumerable GetProjects(CxSASTRestContext ctx, Cancellati return new ProjectReader(jt, ctx, token); } } - , UrlUtils.MakeUrl(ctx.Sast.ApiUrl, URL_SUFFIX) + , UrlUtils.MakeUrl(ctx.Sast.ApiUrl, REST_URL_SUFFIX) , ctx.Sast , token, apiVersion: GetApiVersion(ctx, token) )) return new List(projectReader); diff --git a/XForm/SastTransformer/Transformer.cs b/XForm/SastTransformer/Transformer.cs index 7de7d560..fb2b8f59 100644 --- a/XForm/SastTransformer/Transformer.cs +++ b/XForm/SastTransformer/Transformer.cs @@ -326,7 +326,8 @@ private async Task ResolveScans() _sastVersionTask = GetSASTVersion(); - var projectsTask = Task.Run(() => CxProjects.GetProjects(RestContext, ThreadOpts.CancellationToken), ThreadOpts.CancellationToken); + var projectsTask = Task.Run(() => CxProjects.GetProjects(RestContext, ThreadOpts.CancellationToken, + Config.GetConfig().Overrides.Project), ThreadOpts.CancellationToken); Policies = await policyTask; Teams = await teamsTask;