From e796eb15096e14bfb5e3b8f29c877cd57823e7ea Mon Sep 17 00:00:00 2001 From: Ben Carpenter Date: Tue, 30 Jul 2024 09:09:06 -0700 Subject: [PATCH] Add managed identity option (#56) * update package versions * update DevCenterCredentialsHandler to use managed identity, add IsValidCredentials check * update authconfig for file use * update program, add additional creds options * spelling and formatting --- SurfaceDevCenterManager/Program.cs | 60 ++++++------ .../SurfaceDevCenterManager.csproj | 4 +- .../Utility/DevCenterCredentialsHandler.cs | 93 ++++++++++++++++++- SurfaceDevCenterManager/authconfig.json | 4 +- 4 files changed, 123 insertions(+), 38 deletions(-) diff --git a/SurfaceDevCenterManager/Program.cs b/SurfaceDevCenterManager/Program.cs index 6180209..bf86b72 100644 --- a/SurfaceDevCenterManager/Program.cs +++ b/SurfaceDevCenterManager/Program.cs @@ -130,10 +130,10 @@ private static async Task MainAsync(string[] args) { "h|help", "Show this message and exit", v => show_help = v != null }, { "w|wait", "Wait for submission id to be done", v => WaitOption = true }, { "waitmetadata", "Wait for metadata to be done as well in a submission", v => WaitForMetaData = true }, - { "createmetadata", "Requeset metadata creation for older submissions", v => CreateMetaData = true }, + { "createmetadata", "Request metadata creation for older submissions", v => CreateMetaData = true }, { "a|audience", "List Audiences", v => AudienceOption = true }, { "server=", "Specify target DevCenter server from CredSelect enum", v => { OverrideServer = int.Parse(v); OverrideServerPresent = true; } }, - { "creds=", "Option to specify app credentials. Options: ENVOnly, FileOnly, AADOnly, AADThenFile (Default)", v => CredentialsOption = v }, + { "creds=", "Option to specify app credentials. Options: ENVOnly, ClientCredentials, ManagedIdentity, MiThenFile, FileOnly, AADOnly, AADThenFile (Default)", v => CredentialsOption = v }, { "aad=", "Option to specify AAD auth behavior. Options: Never (Default), Prompt, Always, RefreshSession, SelectAccount", v => AADAuthenticationOption = v }, { "t|timeout=", $"Adjust the timeout for HTTP requests to specified seconds. Default:{DEFAULT_TIMEOUT} seconds", v => TimeoutOption = v }, { "translate", "Translate the given publisherid, productid and submissionid from a partner to the values visible in your HDC account", v => TranslateOption = true}, @@ -231,7 +231,7 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(CreateOption) } { nameof(api.NewProduct) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(CreateOption)} {nameof(api.NewProduct)} experienced a HTTP 429 Too Many Requests response."); return ErrorCodes.HTTP_429_RATE_LIMIT_EXCEEDED; } else @@ -250,7 +250,6 @@ private static async Task MainAsync(string[] args) LogExceptionToConsole(ex, nameof(CreateOption), nameof(api.NewProduct)); return ErrorCodes.PARTNER_CENTER_HTTP_EXCEPTION; } - } else if (DevCenterHWSubmissionType.Submission == createInput.CreateType) { @@ -271,7 +270,7 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(CreateOption) } { nameof(api.NewSubmission) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(CreateOption)} {nameof(api.NewSubmission)} experienced a HTTP 429 Too Many Requests response."); return ErrorCodes.HTTP_429_RATE_LIMIT_EXCEEDED; } else @@ -320,7 +319,7 @@ private static async Task MainAsync(string[] args) { if (retSubmission.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(CreateOption) } { nameof(api.GetSubmission) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(CreateOption)} {nameof(api.GetSubmission)} experienced a HTTP 429 Too Many Requests response."); return ErrorCodes.HTTP_429_RATE_LIMIT_EXCEEDED; } else @@ -390,7 +389,7 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(CreateOption) } { nameof(api.NewShippingLabel) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(CreateOption)} {nameof(api.NewShippingLabel)} experienced a HTTP 429 Too Many Requests response."); return ErrorCodes.HTTP_429_RATE_LIMIT_EXCEEDED; } else @@ -443,12 +442,12 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(CommitOption) } { nameof(api.CommitSubmission) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(CommitOption)} {nameof(api.CommitSubmission)} experienced a HTTP 429 Too Many Requests response."); return ErrorCodes.HTTP_429_RATE_LIMIT_EXCEEDED; } if (ret.Error.Code == ErrorCodeConstants.RequestInvalidForCurrentState && ret.Error.Message == ErrorMessageConstants.OnlyPendingSubmissionsCanBeCommitted) { - Console.WriteLine($"{ nameof(CommitOption) } { nameof(api.CommitSubmission) } request invalid for currentState, { ErrorMessageConstants.OnlyPendingSubmissionsCanBeCommitted }"); + Console.WriteLine($"{nameof(CommitOption)} {nameof(api.CommitSubmission)} request invalid for currentState, {ErrorMessageConstants.OnlyPendingSubmissionsCanBeCommitted}"); DevCenterErrorDetailsDump(ret.Error); return ErrorCodes.COMMIT_REQUEST_INVALID_FOR_CURRENT_STATE; } @@ -469,7 +468,6 @@ private static async Task MainAsync(string[] args) { Console.WriteLine("> Commit OK"); } - } } catch (Exception ex) @@ -496,7 +494,7 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(ListOption) } { nameof(api.GetProducts) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(ListOption)} {nameof(api.GetProducts)} experienced a HTTP 429 Too Many Requests response."); return ErrorCodes.HTTP_429_RATE_LIMIT_EXCEEDED; } else @@ -523,12 +521,12 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(ListOption) } { nameof(api.GetSubmission) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(ListOption)} {nameof(api.GetSubmission)} experienced a HTTP 429 Too Many Requests response."); return ErrorCodes.HTTP_429_RATE_LIMIT_EXCEEDED; } if (ret.Error.Code == ErrorCodeConstants.EntityNotFound) { - Console.WriteLine($"{ nameof(ListOption) } { nameof(api.GetSubmission) } entity not found, try translate option."); + Console.WriteLine($"{nameof(ListOption)} {nameof(api.GetSubmission)} entity not found, try translate option."); DevCenterErrorDetailsDump(ret.Error); return ErrorCodes.SUBMISSION_ENTITY_NOT_FOUND; } @@ -556,7 +554,7 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(ListOption) } { nameof(api.GetShippingLabels) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(ListOption)} {nameof(api.GetShippingLabels)} experienced a HTTP 429 Too Many Requests response."); return ErrorCodes.HTTP_429_RATE_LIMIT_EXCEEDED; } else @@ -583,7 +581,7 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(ListOption) } { nameof(api.GetPartnerSubmission) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(ListOption)} {nameof(api.GetPartnerSubmission)} experienced a HTTP 429 Too Many Requests response."); return ErrorCodes.HTTP_429_RATE_LIMIT_EXCEEDED; } else @@ -655,7 +653,7 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(DownloadOption) } { nameof(api.GetSubmission) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(DownloadOption)} {nameof(api.GetSubmission)} experienced a HTTP 429 Too Many Requests response."); return ErrorCodes.HTTP_429_RATE_LIMIT_EXCEEDED; } else @@ -709,7 +707,7 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(MetadataOption) } { nameof(api.GetSubmission) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(MetadataOption)} {nameof(api.GetSubmission)} experienced a HTTP 429 Too Many Requests response."); return ErrorCodes.HTTP_429_RATE_LIMIT_EXCEEDED; } else @@ -770,7 +768,7 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(SubmissionPackagePath) } { nameof(api.GetSubmission) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(SubmissionPackagePath)} {nameof(api.GetSubmission)} experienced a HTTP 429 Too Many Requests response."); return ErrorCodes.HTTP_429_RATE_LIMIT_EXCEEDED; } else @@ -832,7 +830,7 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(WaitOption) } { nameof(api.GetSubmission) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(WaitOption)} {nameof(api.GetSubmission)} experienced a HTTP 429 Too Many Requests response."); await Task.Delay(5000); continue; } @@ -851,7 +849,7 @@ private static async Task MainAsync(string[] args) { if (sub.WorkflowStatus == null) { - Console.WriteLine($"{ nameof(WaitOption) } { sub.Name } { nameof(WorkflowStatus) } was NULL. Will continue to wait..."); + Console.WriteLine($"{nameof(WaitOption)} {sub.Name} {nameof(WorkflowStatus)} was NULL. Will continue to wait..."); continue; } @@ -930,7 +928,7 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(WaitOption) } { nameof(api.GetShippingLabels) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(WaitOption)} {nameof(api.GetShippingLabels)} experienced a HTTP 429 Too Many Requests response."); await Task.Delay(5000); continue; } @@ -995,7 +993,7 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(AudienceOption) } { nameof(api.GetAudiences) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(AudienceOption)} {nameof(api.GetAudiences)} experienced a HTTP 429 Too Many Requests response."); return ErrorCodes.HTTP_429_RATE_LIMIT_EXCEEDED; } else @@ -1045,7 +1043,7 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(CreateMetaData) } { nameof(api.CreateMetaData) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(CreateMetaData)} {nameof(api.CreateMetaData)} experienced a HTTP 429 Too Many Requests response."); return ErrorCodes.HTTP_429_RATE_LIMIT_EXCEEDED; } else @@ -1106,7 +1104,7 @@ private static async Task MainAsync(string[] args) { if (ret.Error.HttpErrorCode == 429) { - Console.WriteLine($"{ nameof(TranslateOption) } { nameof(api.GetPartnerSubmission) } experienced a HTTP 429 Too Many Requests response."); + Console.WriteLine($"{nameof(TranslateOption)} {nameof(api.GetPartnerSubmission)} experienced a HTTP 429 Too Many Requests response."); return ErrorCodes.HTTP_429_RATE_LIMIT_EXCEEDED; } else @@ -1193,7 +1191,7 @@ private static void DevCenterErrorDetailsDump(DevCenterErrorDetails error) if (error.Trace != null) { Console.WriteLine("Request Id: {0}", error.Trace.RequestId ?? ""); - Console.WriteLine("Method: {0}", error.Trace.Method ?? "" ); + Console.WriteLine("Method: {0}", error.Trace.Method ?? ""); Console.WriteLine("Url: {0}", error.Trace.Url ?? ""); Console.WriteLine("Content: {0}", error.Trace.Content ?? ""); } @@ -1211,12 +1209,12 @@ private static void LogExceptionToConsole(Exception ex, string option, string se Console.WriteLine(""); Console.WriteLine("============================================================"); Console.WriteLine("\tSurfaceDevCenterManager Exception Log"); - Console.WriteLine($"Option: { option ?? "" }"); - Console.WriteLine($"Section: { section ?? "" }"); - Console.WriteLine($"Type: { ex.GetType() ?? null }"); - Console.WriteLine($"Message: { ex.Message ?? "" }"); - Console.WriteLine($"Inner Exception: { ex.InnerException?.Message ?? "" }"); - Console.WriteLine($"Correlation Id: { CorrelationId }"); + Console.WriteLine($"Option: {option ?? ""}"); + Console.WriteLine($"Section: {section ?? ""}"); + Console.WriteLine($"Type: {ex.GetType() ?? null}"); + Console.WriteLine($"Message: {ex.Message ?? ""}"); + Console.WriteLine($"Inner Exception: {ex.InnerException?.Message ?? ""}"); + Console.WriteLine($"Correlation Id: {CorrelationId}"); Console.WriteLine("============================================================"); Console.WriteLine(""); } diff --git a/SurfaceDevCenterManager/SurfaceDevCenterManager.csproj b/SurfaceDevCenterManager/SurfaceDevCenterManager.csproj index 7c140fd..4e55343 100644 --- a/SurfaceDevCenterManager/SurfaceDevCenterManager.csproj +++ b/SurfaceDevCenterManager/SurfaceDevCenterManager.csproj @@ -101,7 +101,7 @@ 5.8.5 - 2.2.13 + 3.0.12 5.3.0 @@ -110,7 +110,7 @@ 6.12.0.148 - 13.0.2 + 13.0.3 4.3.0 diff --git a/SurfaceDevCenterManager/Utility/DevCenterCredentialsHandler.cs b/SurfaceDevCenterManager/Utility/DevCenterCredentialsHandler.cs index d4f51d2..00d01ad 100644 --- a/SurfaceDevCenterManager/Utility/DevCenterCredentialsHandler.cs +++ b/SurfaceDevCenterManager/Utility/DevCenterCredentialsHandler.cs @@ -19,7 +19,7 @@ namespace SurfaceDevCenterManager.Utility { internal class DevCenterCredentialsHandler { - private static readonly byte[] s_aditionalEntropy = { 254, 122, 123, 135, 23, 79, 6 }; + private static readonly byte[] s_additionalEntropy = { 254, 122, 123, 135, 23, 79, 6 }; private static string GetCredential() { @@ -36,7 +36,7 @@ private static string GetCredential() { try { - data = ProtectedData.Unprotect(encryptData, s_aditionalEntropy, DataProtectionScope.CurrentUser); + data = ProtectedData.Unprotect(encryptData, s_additionalEntropy, DataProtectionScope.CurrentUser); retval = Encoding.Unicode.GetString(data); } catch (CryptographicException) @@ -56,7 +56,7 @@ private static bool SetCredential(string token) bool retval = false; try { - encryptData = ProtectedData.Protect(data, s_aditionalEntropy, DataProtectionScope.CurrentUser); + encryptData = ProtectedData.Protect(data, s_additionalEntropy, DataProtectionScope.CurrentUser); } catch (CryptographicException) { @@ -95,7 +95,7 @@ public static async Task> GetApiCreds(stri CredentialsOption = CredentialsOption.ToLowerInvariant(); // Check environment variable option - if (CredentialsOption.CompareTo("envonly") == 0) + if (CredentialsOption.CompareTo("clientcredentials") == 0 || CredentialsOption.CompareTo("envonly") == 0) { try { @@ -111,6 +111,7 @@ public static async Task> GetApiCreds(stri } }; + myCreds = IsValidCredentials(myCreds[0], managedIdentity: false) == true ? myCreds : null; return myCreds; } catch (Exception) @@ -120,6 +121,53 @@ public static async Task> GetApiCreds(stri } } + if (CredentialsOption.CompareTo("managedidentity") == 0) + { + try + { + myCreds = new List + { + new AuthorizationHandlerCredentials() + { + TenantId = Environment.GetEnvironmentVariable("SDCM_CREDS_TENANTID"), + ClientId = Environment.GetEnvironmentVariable("SDCM_CREDS_CLIENTID"), + Url = new Uri(Environment.GetEnvironmentVariable("SDCM_CREDS_URL"), UriKind.Absolute), + UrlPrefix = new Uri(Environment.GetEnvironmentVariable("SDCM_CREDS_URLPREFIX"), UriKind.Relative), + ManagedIdentityClientId = Environment.GetEnvironmentVariable("SDCM_CREDS_MI_CLIENTID"), + Scope = Environment.GetEnvironmentVariable("SDCM_CREDS_MI_SCOPE") + } + }; + + myCreds = IsValidCredentials(myCreds[0], managedIdentity: true) == true ? myCreds : null; + return myCreds; + } + catch (Exception) + { + Console.WriteLine("Missing or invalid environment variables: SDCM_CREDS_TENANTID, SDCM_CREDS_CLIENTID, SDCM_CREDS_MI_CLIENTID, SDCM_CREDS_MI_SCOPE, SDCM_CREDS_URL, SDCM_CREDS_URLPREFIX"); + return null; + } + } + + if (CredentialsOption.CompareTo("mithenfile") == 0) + { + try + { + string authconfig = System.IO.File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "\\authconfig.json"); + myCreds = JsonConvert.DeserializeObject>(authconfig); + if (myCreds.Count == 0) + { + myCreds = null; + } + + myCreds = IsValidCredentials(myCreds[0], managedIdentity: true) == true ? myCreds : null; + return myCreds; + } + catch (Exception) + { + myCreds = null; + } + } + if ((CredentialsOption.CompareTo("aadonly") == 0) || (CredentialsOption.CompareTo("aadthenfile") == 0)) { myCreds = await GetWebApiCreds(AADAuthenticationOption); @@ -273,5 +321,42 @@ private static async Task> FetchList(Uri r return ReturnList; } + + private static bool IsValidCredentials(AuthorizationHandlerCredentials creds, bool managedIdentity) + { + bool isValidCredentials = true; + + if (string.IsNullOrWhiteSpace(creds.TenantId) || creds.TenantId.CompareTo("guid") == 0) + { + return false; + } + + if (string.IsNullOrWhiteSpace(creds.ClientId) || creds.ClientId.CompareTo("guid") == 0) + { + return false; + } + + if (managedIdentity) + { + if (string.IsNullOrWhiteSpace(creds.ManagedIdentityClientId) || creds.ManagedIdentityClientId.CompareTo("guid") == 0) + { + return false; + } + + if (string.IsNullOrWhiteSpace(creds.Scope) || creds.Scope.CompareTo("string") == 0) + { + return false; + } + } + else + { + if (string.IsNullOrWhiteSpace(creds.Key) || creds.Key.CompareTo("string") == 0) + { + return false; + } + } + + return isValidCredentials; + } } } diff --git a/SurfaceDevCenterManager/authconfig.json b/SurfaceDevCenterManager/authconfig.json index 52be4af..56340ff 100644 --- a/SurfaceDevCenterManager/authconfig.json +++ b/SurfaceDevCenterManager/authconfig.json @@ -1,8 +1,10 @@ [ { + "tenantId": "guid", "clientId": "guid", + "managedIdentityClientId": "guid", + "scope": "string", "key": "string", - "tenantId": "guid", "url": "https://manage.devcenter.microsoft.com", "urlPrefix": "v2.0/my" }