diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ffee5ae --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/ConsoleApps/AzureRestApi/AzureRestApi/bin/Debug/net6.0/AzureRestApi.dll", + "args": [], + "cwd": "${workspaceFolder}/ConsoleApps/AzureRestApi/AzureRestApi", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..ee6dee7 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/ConsoleApps/AzureRestApi/AzureRestApi/AzureRestApi.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/ConsoleApps/AzureRestApi/AzureRestApi/AzureRestApi.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/ConsoleApps/AzureRestApi/AzureRestApi/AzureRestApi.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi.sln b/ConsoleApps/SnykRestApi/SnykRestApi.sln new file mode 100644 index 0000000..eb0afa3 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32505.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SnykRestApi", "SnykRestApi\SnykRestApi.csproj", "{388306F9-E67B-4CD2-9876-ACAC06968015}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {388306F9-E67B-4CD2-9876-ACAC06968015}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {388306F9-E67B-4CD2-9876-ACAC06968015}.Debug|Any CPU.Build.0 = Debug|Any CPU + {388306F9-E67B-4CD2-9876-ACAC06968015}.Release|Any CPU.ActiveCfg = Release|Any CPU + {388306F9-E67B-4CD2-9876-ACAC06968015}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BA8B2157-BFB1-4397-9DED-E4522D895591} + EndGlobalSection +EndGlobal diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Parsed/AuditLog.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Parsed/AuditLog.cs new file mode 100644 index 0000000..aeffca4 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Parsed/AuditLog.cs @@ -0,0 +1,15 @@ +namespace SnykRestApi.Models.Parsed +{ + public class AuditLog + { + public string GroupId { get; set; } + public string OrganizationId { get; set; } + public string OrganizationName { get; set; } + public string UserId { get; set; } + public string UserName { get; set; } + public string ProjectId { get; set; } + public string ProjectName { get; set; } + public string Event { get; set; } + public DateTime Created { get; set; } + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/AuditLogResponse.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/AuditLogResponse.cs new file mode 100644 index 0000000..812e0f4 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/AuditLogResponse.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace SnykRestApi.Models.Raw +{ + internal class AuditLogResponse + { + [JsonPropertyName("groupId")] + public string GroupId { get; set; } + [JsonPropertyName("orgId")] + public string OrgId { get; set; } + [JsonPropertyName("userId")] + public string UserId { get; set; } + [JsonPropertyName("projectId")] + public string ProjectId { get; set; } + [JsonPropertyName("event")] + public string Event { get; set; } + //[JsonPropertyName("content")] + //public string Content { get; set; } + [JsonPropertyName("created")] + public DateTime Created { get; set; } + + + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/GroupResponse.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/GroupResponse.cs new file mode 100644 index 0000000..8e8a29f --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/GroupResponse.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace SnykRestApi.Models.Raw +{ + internal class GroupResponse + { + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("id")] + public string Id { get; set; } + + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/OrganizationListResponse.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/OrganizationListResponse.cs new file mode 100644 index 0000000..74eaf77 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/OrganizationListResponse.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace SnykRestApi.Models.Raw +{ + internal class OrganizationListResponse + { + [JsonPropertyName("orgs")] + public List Orgs { get; set; } + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/OrganizationResponse.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/OrganizationResponse.cs new file mode 100644 index 0000000..c868210 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/OrganizationResponse.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace SnykRestApi.Models.Raw +{ + internal class OrganizationResponse + { + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("id")] + public string Id { get; set; } + [JsonPropertyName("slug")] + public string Slug { get; set; } + [JsonPropertyName("url")] + public string Url { get; set; } + [JsonPropertyName("group")] + public GroupResponse Group { get; set; } + + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/ProjectListResponse.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/ProjectListResponse.cs new file mode 100644 index 0000000..752795d --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/ProjectListResponse.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace SnykRestApi.Models.Raw +{ + internal class ProjectListResponse + { + [JsonPropertyName("org")] + public OrganizationResponse Org { get; set; } + [JsonPropertyName("projects")] + public List Projects { get; set; } + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/ProjectResponse.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/ProjectResponse.cs new file mode 100644 index 0000000..0a200d0 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/ProjectResponse.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace SnykRestApi.Models.Raw +{ + internal class ProjectResponse + { + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("id")] + public string Id { get; set; } + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/UserResponse.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/UserResponse.cs new file mode 100644 index 0000000..81b27c5 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/UserResponse.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; + +namespace SnykRestApi.Models.Raw +{ + internal class UserResponse + { + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("id")] + public string Id { get; set; } + [JsonPropertyName("username")] + public string UserName { get; set; } + [JsonPropertyName("email")] + public string Email { get; set; } + + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Settings.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Settings.cs new file mode 100644 index 0000000..44ac9d7 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Settings.cs @@ -0,0 +1,11 @@ +namespace SnykRestApi.Models +{ + public class Settings + { + public string KeyVaultName { get; set; } = string.Empty; + public string CsvFolder { get; set; } = string.Empty; + public string SnykBaseUrl { get; set; } = string.Empty; + + + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Program.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Program.cs new file mode 100644 index 0000000..5181c43 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Program.cs @@ -0,0 +1,40 @@ +using SnykRestApi.Models; +using SnykRestApi.Repositories; +using SnykRestApi.Services; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace SnykRestApi +{ + class Program + { + static Task Main(string[] args) => + CreateHostBuilder(args).Build().RunAsync(); + + static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureServices((builder, services) => + { + IConfiguration config = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddEnvironmentVariables() + .Build(); + + Settings settings = config.GetRequiredSection("Settings").Get(); + + services.AddSingleton(); + services.AddSingleton(settings); + + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); + services.AddTransient(); + + services.AddScoped(); + + services.AddHostedService(); + }); + } +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/AccessTokenRepository.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/AccessTokenRepository.cs new file mode 100644 index 0000000..f9d3ba6 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/AccessTokenRepository.cs @@ -0,0 +1,29 @@ +using Azure.Identity; +using Azure.Security.KeyVault.Secrets; +using SnykRestApi.Models; + +namespace SnykRestApi.Repositories +{ + public class AccessTokenRepository + { + private readonly Settings _settings; + private string? _authorizationToken = string.Empty; + + public AccessTokenRepository(Settings settings) + { + _settings = settings; + } + + public async Task GetAuthorizationToken() + { + if (!string.IsNullOrWhiteSpace(_authorizationToken)) return _authorizationToken; + + var keyvaultUri = "https://" + _settings.KeyVaultName + ".vault.azure.net"; + var credential = new DefaultAzureCredential(); + var client = new SecretClient(new Uri(keyvaultUri), credential); + _authorizationToken = (await client.GetSecretAsync("SnykKey")).Value.Value; + + return _authorizationToken; + } + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/AuditLogRepository.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/AuditLogRepository.cs new file mode 100644 index 0000000..ea64f56 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/AuditLogRepository.cs @@ -0,0 +1,49 @@ +using SnykRestApi.Models; +using SnykRestApi.Models.Raw; +using System.Net.Http.Headers; +using System.Text.Json; + +namespace SnykRestApi.Repositories +{ + public class AuditLogRepository + { + private readonly HttpClient _httpClient; + private readonly AccessTokenRepository _accessTokenRepository; + private readonly Settings _settings; + + public AuditLogRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository, Settings settings) + { + _httpClient = httpClient; + _accessTokenRepository = accessTokenRepository; + _settings = settings; + } + + internal async Task> GetByOrganizationId(string origanizationId) + { + var authorizationToken = await _accessTokenRepository.GetAuthorizationToken(); + List result = new(); + List responseItems; + + int page = 0; + + do + { + HttpRequestMessage request = new(HttpMethod.Post, $"{_settings.SnykBaseUrl}org/{origanizationId}/audit?from=2022-07-01&page={++page}"); + request.Headers.Accept.Clear(); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Authorization = new AuthenticationHeaderValue("token", authorizationToken); + + var response = await _httpClient.SendAsync(request).ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + + var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + responseItems = JsonSerializer.Deserialize>(responseString) ?? new List(); + result.AddRange(responseItems); + } + while (responseItems.Count == 100); + + return result; + } + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/CsvRepository.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/CsvRepository.cs new file mode 100644 index 0000000..d988c77 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/CsvRepository.cs @@ -0,0 +1,32 @@ +using CsvHelper; +using SnykRestApi.Models; +using SnykRestApi.Models.Parsed; +using System.Globalization; + +namespace SnykRestApi.Repositories +{ + public class CsvRepository + { + private readonly Settings _settings; + + public CsvRepository(Settings settings) + { + _settings = settings; + } + + public async Task WriteAll (List log) + { + var t = DateTime.Now; + var fileName = $"{_settings.CsvFolder}SnykAuditLog_{t:yyyy}{t:MM}{t:dd}_{t:HH}{t:mm}{t:ss}_{t:FFF}.csv"; + + using (var writer = new StreamWriter(fileName)) + using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) + { + await csv.WriteRecordsAsync(log); + } + + + + } + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/OrganizationRepository.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/OrganizationRepository.cs new file mode 100644 index 0000000..e3dee25 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/OrganizationRepository.cs @@ -0,0 +1,40 @@ +using SnykRestApi.Models; +using SnykRestApi.Models.Raw; +using System.Net.Http.Headers; +using System.Text.Json; + +namespace SnykRestApi.Repositories +{ + public class OrganizationRepository + { + private readonly HttpClient _httpClient; + private readonly AccessTokenRepository _accessTokenRepository; + private readonly Settings _settings; + + public OrganizationRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository, Settings settings) + { + _httpClient = httpClient; + _accessTokenRepository = accessTokenRepository; + _settings = settings; + } + + internal async Task> GetAll() + { + var authorizationToken = await _accessTokenRepository.GetAuthorizationToken(); + + var uri = $"{_settings.SnykBaseUrl}orgs"; + HttpRequestMessage request = new(HttpMethod.Get, uri); + request.Headers.Accept.Clear(); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Authorization = new AuthenticationHeaderValue("token", authorizationToken); + + var response = await _httpClient.SendAsync(request).ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + + var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var result = JsonSerializer.Deserialize(responseString)?.Orgs; + return result ?? new List(); + } + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/ProjectRepository.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/ProjectRepository.cs new file mode 100644 index 0000000..8c981f8 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/ProjectRepository.cs @@ -0,0 +1,40 @@ +using SnykRestApi.Models; +using SnykRestApi.Models.Raw; +using System.Net.Http.Headers; +using System.Text.Json; + +namespace SnykRestApi.Repositories +{ + public class ProjectRepository + { + private readonly HttpClient _httpClient; + private readonly AccessTokenRepository _accessTokenRepository; + private readonly Settings _settings; + + public ProjectRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository, Settings settings) + { + _httpClient = httpClient; + _accessTokenRepository = accessTokenRepository; + _settings = settings; + } + + internal async Task> GetAll(string organizationId) + { + var authorizationToken = await _accessTokenRepository.GetAuthorizationToken(); + + var uri = $"{_settings.SnykBaseUrl}org/{organizationId}/projects"; + HttpRequestMessage request = new(HttpMethod.Post, uri); + request.Headers.Accept.Clear(); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Authorization = new AuthenticationHeaderValue("token", authorizationToken); + + var response = await _httpClient.SendAsync(request).ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + + var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var result = JsonSerializer.Deserialize(responseString)?.Projects; + return result ?? new List(); + } + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/UserRepository.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/UserRepository.cs new file mode 100644 index 0000000..338dbc3 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/UserRepository.cs @@ -0,0 +1,65 @@ +using SnykRestApi.Models; +using SnykRestApi.Models.Raw; +using Spectre.Console; +using System.Net.Http.Headers; +using System.Text.Json; + +namespace SnykRestApi.Repositories +{ + public class UserRepository + { + private readonly HttpClient _httpClient; + private readonly AccessTokenRepository _accessTokenRepository; + private readonly Settings _settings; + + public UserRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository, Settings settings) + { + _httpClient = httpClient; + _accessTokenRepository = accessTokenRepository; + _settings = settings; + } + + internal async Task> GetAll(List ids) + { + var authorizationToken = await _accessTokenRepository.GetAuthorizationToken(); + List result = new(); + UserResponse? responseItem; + + foreach (var id in ids) + { + HttpRequestMessage request = new(HttpMethod.Get, $"{_settings.SnykBaseUrl}user/{id}"); + request.Headers.Accept.Clear(); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Authorization = new AuthenticationHeaderValue("token", authorizationToken); + + var response = await _httpClient.SendAsync(request).ConfigureAwait(false); + + try + { + response.EnsureSuccessStatusCode(); + + var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + responseItem = JsonSerializer.Deserialize(responseString); + + if (responseItem != null) + { + result.Add(responseItem); + } + } + catch + { + AnsiConsole.MarkupLine($"[red bold]Could not find user with id '{id}'[/]"); + result.Add(new() + { + Id = id, + Name = "{ Unknown user }", + UserName = "{ Unknown user }", + Email = "{ Unknown user }" + }); + } + } + + return result; + } + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Services/AuditLogService.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Services/AuditLogService.cs new file mode 100644 index 0000000..d6de2f5 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Services/AuditLogService.cs @@ -0,0 +1,109 @@ +using SnykRestApi.Models.Parsed; +using SnykRestApi.Models.Raw; +using SnykRestApi.Repositories; +using Spectre.Console; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SnykRestApi.Services +{ + public class AuditLogService + { + private readonly OrganizationRepository _organizationRepository; + private readonly AuditLogRepository _auditLogRepository; + private readonly ProjectRepository _projectRepository; + private readonly UserRepository _userRepository; + private readonly CsvRepository _csvRepository; + + public AuditLogService(OrganizationRepository organizationRepository, AuditLogRepository auditLogRepostitory, ProjectRepository projectRepository, UserRepository userRepository, CsvRepository csvRepository) + { + _organizationRepository = organizationRepository; + _auditLogRepository = auditLogRepostitory; + _projectRepository = projectRepository; + _userRepository = userRepository; + _csvRepository = csvRepository; + } + + public async Task CreateAuditLog () + { + var rule = new Rule("[skyblue1]Creating Snyk Audit Log CSV[/]"); + rule.Alignment = Justify.Left; + rule.Style = Style.Parse("skyblue1"); + AnsiConsole.Write(rule); + + await AnsiConsole.Status() + .AutoRefresh(true) + .Spinner(Spinner.Known.Default) + .StartAsync("Retrieving organizations...", async ctx => + { + var organizations = await _organizationRepository.GetAll(); + if (organizations == null || !organizations.Any()) throw new Exception("No organizations found"); + + var log = new List(); + var projects = new List(); + + foreach (var organization in organizations) + { + rule = new Rule($"[skyblue1]{organization.Name}[/]"); + rule.Alignment = Justify.Left; + rule.Style = Style.Parse("skyblue1 dim"); + AnsiConsole.Write(rule); + + ctx.Status($"Getting the projects for organization '{organization.Name}'"); + var orgProjects = await _projectRepository.GetAll(organization.Id); + projects.AddRange(orgProjects); + AnsiConsole.WriteLine($"Got {orgProjects.Count} projects."); + + ctx.Status($"Getting the audit log for organization '{organization.Name}'"); + var orgLogs = await _auditLogRepository.GetByOrganizationId(organization.Id); + log.AddRange(orgLogs); + AnsiConsole.WriteLine($"Got {orgLogs.Count} log records."); + } + + rule = new Rule($"[skyblue1]Retrieving users[/]"); + rule.Alignment = Justify.Left; + rule.Style = Style.Parse("skyblue1 dim"); + AnsiConsole.Write(rule); + + ctx.Status($"Getting users"); + var userIds = log.Select(l => l.UserId).Distinct().ToList(); + + var users = await _userRepository.GetAll(userIds); + AnsiConsole.WriteLine($"Got {users.Count} users of {userIds.Count} user ids."); + + rule = new Rule($"[skyblue1]Creating CSV[/]"); + rule.Alignment = Justify.Left; + rule.Style = Style.Parse("skyblue1 dim"); + AnsiConsole.Write(rule); + + ctx.Status($"Combining all information"); + var result = (from l in log + join o in organizations on l.OrgId equals o.Id into gjO + from subO in gjO.DefaultIfEmpty() + join u in users on l.UserId equals u.Id into gjU + from subU in gjU.DefaultIfEmpty() + join p in projects on l.ProjectId equals p.Id into gjP + from subP in gjP.DefaultIfEmpty() + select new AuditLog() + { + GroupId = l.GroupId, + OrganizationId = l.OrgId, + OrganizationName = subO?.Name, + ProjectId = l.ProjectId, + ProjectName = subP?.Name, + UserId = l.UserId, + UserName = subU?.Name, + Event = l.Event, + Created = l.Created + }).ToList(); + AnsiConsole.WriteLine($"Prepared {result.Count} lines to export of {log.Count} audit log records."); + + ctx.Status($"Writing CSV"); + await _csvRepository.WriteAll(result); + }); + } + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Services/OptionService.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Services/OptionService.cs new file mode 100644 index 0000000..cee5150 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Services/OptionService.cs @@ -0,0 +1,56 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Spectre.Console; + +namespace SnykRestApi.Services +{ + public class OptionService : IHostedService + { + private readonly ILogger _logger; + private readonly AuditLogService _auditLogService; + + public OptionService(ILogger logger, AuditLogService auditLogService) + { + _logger = logger; + _auditLogService = auditLogService; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + var rule = new Rule("[yellow]Cloud Egineering Console App[/]"); + AnsiConsole.Write(rule); + + + Console.WriteLine("-- This couldn't be done in the Snyk UI, so here we are.... "); + Console.WriteLine(); + + var choices = new[] + { + "Create Audit log CSV.", + "Exit" + }; + var result = AnsiConsole.Prompt(new SelectionPrompt() + .Title("Select what you want to do:") + .PageSize(10) + .MoreChoicesText("[grey](Move up and down to reveal more choices)[/]") + .AddChoices(choices)); + + if (result == choices[0]) + { + await _auditLogService.CreateAuditLog(); + } + //else if (result == choices[1]) + //{ + // // Do something + //} + + rule = new Rule("[yellow]Done. Bye.[/]"); + AnsiConsole.Write(rule); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/SnykRestApi.csproj b/ConsoleApps/SnykRestApi/SnykRestApi/SnykRestApi.csproj new file mode 100644 index 0000000..4917a9d --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/SnykRestApi.csproj @@ -0,0 +1,28 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/appsettings.json b/ConsoleApps/SnykRestApi/SnykRestApi/appsettings.json new file mode 100644 index 0000000..5cf9ee9 --- /dev/null +++ b/ConsoleApps/SnykRestApi/SnykRestApi/appsettings.json @@ -0,0 +1,14 @@ +{ + "Settings": { + "KeyVaultName": "consoleapp", + "CsvFolder": "c:\\temp\\", + "SnykBaseUrl": "https://api.snyk.io/api/v1/" + }, + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "None" + } + } +} \ No newline at end of file