From ea4826348c6b3bda30630baf2f7e1023276c98c1 Mon Sep 17 00:00:00 2001 From: "Sam Smith (Microsoft)" Date: Wed, 28 Apr 2021 17:05:19 -0400 Subject: [PATCH] Added code to create new settings --- .../Controllers/BuildsController.cs | 4 + .../DeploymentFrequencyController.cs | 4 + .../LeadTimeForChangesController.cs | 4 + .../Controllers/PullRequestsController.cs | 7 ++ .../Controllers/SettingsController.cs | 22 +++-- .../Utility/SecretsProcessing.cs | 15 ++++ .../Service/SecretsProcessingTests.cs | 77 +++++++++++++++++ .../Controllers/HomeController.cs | 44 ++++++++++ .../Services/ServiceAPIClient.cs | 20 ++++- .../Views/Home/AddAzureDevOpsSetting.cshtml | 83 +++++++++++++++++++ .../Views/Home/AddGitHubSetting.cshtml | 80 ++++++++++++++++++ .../Views/Home/Settings.cshtml | 15 +++- 12 files changed, 364 insertions(+), 11 deletions(-) create mode 100644 src/DevOpsMetrics.Service/Utility/SecretsProcessing.cs create mode 100644 src/DevOpsMetrics.Tests/Service/SecretsProcessingTests.cs create mode 100644 src/DevOpsMetrics.Web/Views/Home/AddAzureDevOpsSetting.cshtml create mode 100644 src/DevOpsMetrics.Web/Views/Home/AddGitHubSetting.cshtml diff --git a/src/DevOpsMetrics.Service/Controllers/BuildsController.cs b/src/DevOpsMetrics.Service/Controllers/BuildsController.cs index 3e7e436c..06e7acab 100644 --- a/src/DevOpsMetrics.Service/Controllers/BuildsController.cs +++ b/src/DevOpsMetrics.Service/Controllers/BuildsController.cs @@ -6,6 +6,7 @@ using DevOpsMetrics.Core.Models.AzureDevOps; using DevOpsMetrics.Core.Models.Common; using DevOpsMetrics.Core.Models.GitHub; +using DevOpsMetrics.Service.Utility; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -38,6 +39,7 @@ public async Task UpdateAzureDevOpsBuilds( //Get the PAT token from the key vault string patTokenName = PartitionKeys.CreateAzureDevOpsSettingsPartitionKeyPatToken(organization, project, repository); + patTokenName = SecretsProcessing.CleanKey(patTokenName); string patToken = Configuration[patTokenName]; if (string.IsNullOrEmpty(patToken) == true) { @@ -73,7 +75,9 @@ public async Task UpdateGitHubActionRuns( //Get the client id and secret from the settings string clientIdName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientId(owner, repo); + clientIdName = SecretsProcessing.CleanKey(clientIdName); string clientSecretName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientSecret(owner, repo); + clientSecretName = SecretsProcessing.CleanKey(clientSecretName); string clientId = Configuration[clientIdName]; string clientSecret = Configuration[clientSecretName]; if (string.IsNullOrEmpty(clientId) == true | string.IsNullOrEmpty(clientSecret) == true) diff --git a/src/DevOpsMetrics.Service/Controllers/DeploymentFrequencyController.cs b/src/DevOpsMetrics.Service/Controllers/DeploymentFrequencyController.cs index f4afe647..d4dcb198 100644 --- a/src/DevOpsMetrics.Service/Controllers/DeploymentFrequencyController.cs +++ b/src/DevOpsMetrics.Service/Controllers/DeploymentFrequencyController.cs @@ -6,6 +6,7 @@ using DevOpsMetrics.Core.Models.AzureDevOps; using DevOpsMetrics.Core.Models.Common; using DevOpsMetrics.Core.Models.GitHub; +using DevOpsMetrics.Service.Utility; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -35,6 +36,7 @@ public async Task GetAzureDevOpsDeploymentFrequency(bo //Get the PAT token from the key vault string patTokenName = PartitionKeys.CreateAzureDevOpsSettingsPartitionKeyPatToken(organization, project, repository); + patTokenName = SecretsProcessing.CleanKey(patTokenName); string patToken = Configuration[patTokenName]; if (string.IsNullOrEmpty(patToken) == true) { @@ -72,7 +74,9 @@ public async Task GetGitHubDeploymentFrequency(bool ge //Get the client id and secret from the settings string clientIdName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientId(owner, repo); + clientIdName = SecretsProcessing.CleanKey(clientIdName); string clientSecretName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientSecret(owner, repo); + clientSecretName = SecretsProcessing.CleanKey(clientSecretName); string clientId = Configuration[clientIdName]; string clientSecret = Configuration[clientSecretName]; if (string.IsNullOrEmpty(clientId) == true | string.IsNullOrEmpty(clientSecret) == true) diff --git a/src/DevOpsMetrics.Service/Controllers/LeadTimeForChangesController.cs b/src/DevOpsMetrics.Service/Controllers/LeadTimeForChangesController.cs index 83194412..75bcb6a2 100644 --- a/src/DevOpsMetrics.Service/Controllers/LeadTimeForChangesController.cs +++ b/src/DevOpsMetrics.Service/Controllers/LeadTimeForChangesController.cs @@ -6,6 +6,7 @@ using DevOpsMetrics.Core.Models.AzureDevOps; using DevOpsMetrics.Core.Models.Common; using DevOpsMetrics.Core.Models.GitHub; +using DevOpsMetrics.Service.Utility; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -35,6 +36,7 @@ public async Task GetAzureDevOpsLeadTimeForChanges(bool //Get the PAT token from the key vault string patTokenName = PartitionKeys.CreateAzureDevOpsSettingsPartitionKeyPatToken(organization, project, repository); + patTokenName = SecretsProcessing.CleanKey(patTokenName); string patToken = Configuration[patTokenName]; if (string.IsNullOrEmpty(patToken) == true) { @@ -73,7 +75,9 @@ public async Task GetGitHubLeadTimeForChanges(bool getS //Get the client id and secret from the settings string clientIdName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientId(owner, repo); + clientIdName = SecretsProcessing.CleanKey(clientIdName); string clientSecretName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientSecret(owner, repo); + clientSecretName = SecretsProcessing.CleanKey(clientSecretName); string clientId = Configuration[clientIdName]; string clientSecret = Configuration[clientSecretName]; if (string.IsNullOrEmpty(clientId) == true | string.IsNullOrEmpty(clientSecret) == true) diff --git a/src/DevOpsMetrics.Service/Controllers/PullRequestsController.cs b/src/DevOpsMetrics.Service/Controllers/PullRequestsController.cs index 4febaafa..49b684b2 100644 --- a/src/DevOpsMetrics.Service/Controllers/PullRequestsController.cs +++ b/src/DevOpsMetrics.Service/Controllers/PullRequestsController.cs @@ -6,6 +6,7 @@ using DevOpsMetrics.Core.Models.AzureDevOps; using DevOpsMetrics.Core.Models.Common; using DevOpsMetrics.Core.Models.GitHub; +using DevOpsMetrics.Service.Utility; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -36,6 +37,7 @@ public async Task UpdateAzureDevOpsPullRequests( //Get the PAT token from the key vault string patTokenName = PartitionKeys.CreateAzureDevOpsSettingsPartitionKeyPatToken(organization, project, repository); + patTokenName = SecretsProcessing.CleanKey(patTokenName); string patToken = Configuration[patTokenName]; if (string.IsNullOrEmpty(patToken) == true) { @@ -71,7 +73,9 @@ public async Task UpdateGitHubActionPullRequests( //Get the client id and secret from the settings string clientIdName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientId(owner, repo); + clientIdName = SecretsProcessing.CleanKey(clientIdName); string clientSecretName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientSecret(owner, repo); + clientSecretName = SecretsProcessing.CleanKey(clientSecretName); string clientId = Configuration[clientIdName]; string clientSecret = Configuration[clientSecretName]; if (string.IsNullOrEmpty(clientId) == true | string.IsNullOrEmpty(clientSecret) == true) @@ -108,6 +112,7 @@ public async Task UpdateAzureDevOpsPullRequestCommits( //Get the PAT token from the key vault string patTokenName = PartitionKeys.CreateAzureDevOpsSettingsPartitionKeyPatToken(organization, project, repository); + patTokenName = SecretsProcessing.CleanKey(patTokenName); string patToken = Configuration[patTokenName]; if (string.IsNullOrEmpty(patToken) == true) { @@ -142,7 +147,9 @@ public async Task UpdateGitHubActionPullRequestCommits( //Get the client id and secret from the settings string clientIdName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientId(owner, repo); + clientIdName = SecretsProcessing.CleanKey(clientIdName); string clientSecretName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientSecret(owner, repo); + clientSecretName = SecretsProcessing.CleanKey(clientSecretName); string clientId = Configuration[clientIdName]; string clientSecret = Configuration[clientSecretName]; if (string.IsNullOrEmpty(clientId) == true | string.IsNullOrEmpty(clientSecret) == true) diff --git a/src/DevOpsMetrics.Service/Controllers/SettingsController.cs b/src/DevOpsMetrics.Service/Controllers/SettingsController.cs index aafbceba..b90c778a 100644 --- a/src/DevOpsMetrics.Service/Controllers/SettingsController.cs +++ b/src/DevOpsMetrics.Service/Controllers/SettingsController.cs @@ -7,6 +7,7 @@ using DevOpsMetrics.Core.Models.AzureDevOps; using DevOpsMetrics.Core.Models.Common; using DevOpsMetrics.Core.Models.GitHub; +using DevOpsMetrics.Service.Utility; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -47,10 +48,11 @@ public async Task UpdateAzureDevOpsSetting(string patToken, string branch, string buildName, string buildId, string resourceGroup, int itemOrder) { //Save the PAT token to the key vault - string patTokenSecretName = PartitionKeys.CreateAzureDevOpsSettingsPartitionKeyPatToken(organization, project, repository); - if (patTokenSecretName.Length > 12) + string patTokenName = PartitionKeys.CreateAzureDevOpsSettingsPartitionKeyPatToken(organization, project, repository); + patTokenName = SecretsProcessing.CleanKey(patTokenName); + if (patTokenName.Length > 12) { - await CreateKeyVaultSecret(patTokenSecretName, patToken); + await CreateKeyVaultSecret(patTokenName, patToken); } //Save everything else to table storage @@ -65,15 +67,17 @@ public async Task UpdateGitHubSetting(string clientId, string clientSecret string branch, string workflowName, string workflowId, string resourceGroup, int itemOrder) { //Save the Client Id and Client Secret to the key vault - string clientIdSecretName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientId(owner, repo); - if (clientIdSecretName.Length > 10) + string clientIdName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientId(owner, repo); + clientIdName = SecretsProcessing.CleanKey(clientIdName); + if (clientIdName.Length > 10) { - await CreateKeyVaultSecret(clientIdSecretName, clientId); + await CreateKeyVaultSecret(clientIdName, clientId); } - string clientSecretSecretName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientSecret(owner, repo); - if (clientSecretSecretName.Length > 14) + string clientSecretName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientSecret(owner, repo); + clientSecretName = SecretsProcessing.CleanKey(clientSecretName); + if (clientSecretName.Length > 14) { - await CreateKeyVaultSecret(clientSecretSecretName, clientSecret); + await CreateKeyVaultSecret(clientSecretName, clientSecret); } //Save everything else to table storage diff --git a/src/DevOpsMetrics.Service/Utility/SecretsProcessing.cs b/src/DevOpsMetrics.Service/Utility/SecretsProcessing.cs new file mode 100644 index 00000000..6890c7c7 --- /dev/null +++ b/src/DevOpsMetrics.Service/Utility/SecretsProcessing.cs @@ -0,0 +1,15 @@ +using System.Text.RegularExpressions; + +namespace DevOpsMetrics.Service.Utility +{ + public static class SecretsProcessing + { + //https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftkeyvault + //Only Alphanumerics and hyphens allowed + public static string CleanKey(string name) + { + return Regex.Replace(name, @"[^a-zA-Z0-9]+", "-").Trim('-'); + } + + } +} diff --git a/src/DevOpsMetrics.Tests/Service/SecretsProcessingTests.cs b/src/DevOpsMetrics.Tests/Service/SecretsProcessingTests.cs new file mode 100644 index 00000000..98ff621b --- /dev/null +++ b/src/DevOpsMetrics.Tests/Service/SecretsProcessingTests.cs @@ -0,0 +1,77 @@ +using DevOpsMetrics.Service.Utility; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace DevOpsMetrics.Tests.Service +{ + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [TestCategory("L0Test")] + [TestClass] + public class SecretsProcessingTests + { + [TestMethod] + public void SecretWithDotTest() + { + //Arrange + string name = "SamLearnsAzure.CI"; + + //Act + string result = SecretsProcessing.CleanKey(name); + + //Assert + Assert.AreEqual("SamLearnsAzure-CI", result); + } + + [TestMethod] + public void SecretWithSpaceTest() + { + //Arrange + string name = "SamLearnsAzure CI"; + + //Act + string result = SecretsProcessing.CleanKey(name); + + //Assert + Assert.AreEqual("SamLearnsAzure-CI", result); + } + + [TestMethod] + public void SecretWithColonTest() + { + //Arrange + string name = "SamLearnsAzure:CI"; + + //Act + string result = SecretsProcessing.CleanKey(name); + + //Assert + Assert.AreEqual("SamLearnsAzure-CI", result); + } + + [TestMethod] + public void SecretWithQuestionTest() + { + //Arrange + string name = "SamLearnsAzure?CI"; + + //Act + string result = SecretsProcessing.CleanKey(name); + + //Assert + Assert.AreEqual("SamLearnsAzure-CI", result); + } + + [TestMethod] + public void SecretThatIsValidTest() + { + //Arrange + string name = "SamLearnsAzure123abcCI"; + + //Act + string result = SecretsProcessing.CleanKey(name); + + //Assert + Assert.AreEqual("SamLearnsAzure123abcCI", result); + } + + } +} diff --git a/src/DevOpsMetrics.Web/Controllers/HomeController.cs b/src/DevOpsMetrics.Web/Controllers/HomeController.cs index 0eb90e97..42477c77 100644 --- a/src/DevOpsMetrics.Web/Controllers/HomeController.cs +++ b/src/DevOpsMetrics.Web/Controllers/HomeController.cs @@ -527,6 +527,50 @@ public async Task Settings() return View(result); } + [HttpGet] + public IActionResult AddAzureDevOpsSetting() + { + return View(); + } + + [HttpPost] + public async Task UpdateAzureDevOpsSetting(string patToken, + string organization, string project, string repository, + string branch, string buildName, string buildId, string resourceGroup, int itemOrder) + { + //Find the right project to load + ServiceApiClient serviceApiClient = new ServiceApiClient(Configuration); + if (patToken != null) + { + + await serviceApiClient.UpdateAzureDevOpsSetting(patToken, + organization, project, repository, + branch, buildName, buildId, resourceGroup, itemOrder); + } + + return RedirectToAction("Settings", "Home"); + } + + [HttpGet] + public IActionResult AddGitHubSetting() + { + return View(); + } + + [HttpPost] + public async Task UpdateGitHubSetting(string clientId, string clientSecret, + string owner, string repo, + string branch, string workflowName, string workflowId, string resourceGroup, int itemOrder) + { + //Find the right project to load + ServiceApiClient serviceApiClient = new ServiceApiClient(Configuration); + await serviceApiClient.UpdateGitHubSetting(clientId, clientSecret, + owner, repo, + branch, workflowName, workflowId, resourceGroup, itemOrder); + + return RedirectToAction("Settings", "Home"); + } + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { diff --git a/src/DevOpsMetrics.Web/Services/ServiceAPIClient.cs b/src/DevOpsMetrics.Web/Services/ServiceAPIClient.cs index 5f02f5df..98d7fb42 100644 --- a/src/DevOpsMetrics.Web/Services/ServiceAPIClient.cs +++ b/src/DevOpsMetrics.Web/Services/ServiceAPIClient.cs @@ -55,7 +55,7 @@ public async Task GetAzureMeanTimeToRestore(bool getSamp return await GetResponse(Client, url); } - public async Task GetChangeFailureRate(bool getSampleData, DevOpsPlatform targetDevOpsPlatform, string organization_owner, string project_repo, string branch, string buildName_workflowName, int numberOfDays, int maxNumberOfItems) + public async Task GetChangeFailureRate(bool getSampleData, DevOpsPlatform targetDevOpsPlatform, string organization_owner, string project_repo, string branch, string buildName_workflowName, int numberOfDays, int maxNumberOfItems) { string url = $"/api/ChangeFailureRate/GetChangeFailureRate?getSampleData={getSampleData}&targetDevOpsPlatform={targetDevOpsPlatform}&organization_owner={organization_owner}&project_repo={project_repo}&branch={branch}&buildName_workflowName={buildName_workflowName}&numberOfDays={numberOfDays}&maxNumberOfItems={maxNumberOfItems}"; return await GetResponse(Client, url); @@ -79,6 +79,24 @@ public async Task> GetGitHubSettings() return await GetResponse>(Client, url); } + public async Task UpdateAzureDevOpsSetting(string patToken, + string organization, string project, string repository, + string branch, string buildName, string buildId, string resourceGroup, int itemOrder) + { + string url = $"/api/Settings/UpdateAzureDevOpsSetting?patToken={patToken}&organization={organization}&project={project}&repository={repository}&branch={branch}&buildName={buildName}&buildId={buildId}&resourceGroup={resourceGroup}&itemOrder={itemOrder}"; + + return await GetResponse(Client, url); + } + + public async Task UpdateGitHubSetting(string clientId, string clientSecret, + string owner, string repo, + string branch, string workflowName, string workflowId, string resourceGroup, int itemOrder) + { + string url = $"/api/Settings/UpdateGitHubSetting?clientId={clientId}&clientSecret={clientSecret}&owner={owner}&repo={repo}&branch={branch}&workflowName={workflowName}&workflowId={workflowId}&resourceGroup={resourceGroup}&itemOrder={itemOrder}"; + + return await GetResponse(Client, url); + } + public async Task> GetAzureDevOpsProjectLogs(string organization, string project, string repository) { string url = $"/api/Settings/GetAzureDevOpsProjectLog?organization={organization}&project={project}&repository={repository}"; diff --git a/src/DevOpsMetrics.Web/Views/Home/AddAzureDevOpsSetting.cshtml b/src/DevOpsMetrics.Web/Views/Home/AddAzureDevOpsSetting.cshtml new file mode 100644 index 00000000..bc30ebcb --- /dev/null +++ b/src/DevOpsMetrics.Web/Views/Home/AddAzureDevOpsSetting.cshtml @@ -0,0 +1,83 @@ +@{ + ViewData["Title"] = "Add Azure DevOps setting"; +} + +
+
+

Add new Azure DevOps setting

+ +
+
+

  Azure DevOps

+ Add new Azure DevOps setting
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Pat Token: + + +
+ organization: + + +
+ project: + + +
+ repository: + + +
+ branch: + + +
+ buildName: + + +
+ buildId: + + +
+ resourceGroup: + + +
+ + +
+
+
\ No newline at end of file diff --git a/src/DevOpsMetrics.Web/Views/Home/AddGitHubSetting.cshtml b/src/DevOpsMetrics.Web/Views/Home/AddGitHubSetting.cshtml new file mode 100644 index 00000000..3720f5bc --- /dev/null +++ b/src/DevOpsMetrics.Web/Views/Home/AddGitHubSetting.cshtml @@ -0,0 +1,80 @@ +@{ + ViewData["Title"] = "Add GitHub settings"; +} + +
+
+

View settings (secrets hidden)

+ +

  GitHub

+ Add new GitHub setting
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Client id: + + +
+ Client secret: + + +
+ Owner: + + +
+ Repo: + + +
+ Branch: + + +
+ Workflow Name: + + +
+ Workflow Id: + + +
+ Resource Group: + + +
+ + +
+
\ No newline at end of file diff --git a/src/DevOpsMetrics.Web/Views/Home/Settings.cshtml b/src/DevOpsMetrics.Web/Views/Home/Settings.cshtml index 0819b68f..39a81fd4 100644 --- a/src/DevOpsMetrics.Web/Views/Home/Settings.cshtml +++ b/src/DevOpsMetrics.Web/Views/Home/Settings.cshtml @@ -13,18 +13,21 @@

  Azure DevOps

+ Add new Azure DevOps setting
- + @foreach (AzureDevOpsSettings item in Model.Item1) { @@ -38,11 +41,15 @@ + }
Organization Project Repository + Delete? +
@item.Repository + Delete +

  GitHub

+ Add new GitHub setting
+ @foreach (GitHubSettings item in Model.Item2) { @@ -61,6 +71,9 @@ + }
@@ -51,6 +58,9 @@ Repo + Delete? +
@item.Repo + Delete +