From be7605600b804cfcb40fd928c757c900642ab273 Mon Sep 17 00:00:00 2001 From: Riad <1711217+RiadGahlouz@users.noreply.github.com> Date: Wed, 19 Oct 2022 12:28:06 -0700 Subject: [PATCH] [ReleasePrep][2022.10.19]RI of dev into main (#9280) * Update NuGetGallery.Services to address CG alerts (#9274) * Address accessibility for syntax highlighting (#9273) * address accessbility for syntax highlighting * Fix Support link (#9276) * fix broken support link * Update Download table heading (#9267) * update correct place * update table header placement * Add instructions to install MSBuildSdk packages (#9268) * Added "IsMSBuildSdkPackageType" to determine whether a package is of type MSBuildSdk. DisplayPackage view modified to show specific instructions for SDK types in project files as per #8800 * Changed "Include" to correct attribute "Name" for SDK package type Co-authored-by: Advay Tandon <82980589+advay26@users.noreply.github.com> Co-authored-by: lyndaidaii <64443925+lyndaidaii@users.noreply.github.com> * [CodeQL] Suppress CSRF token validation warnings (#9278) * Added CSRF token checks to address CodeQL bugs * Added CodeQL suppressions * Make thinner border for focused links (#9277) * Make thiner border for focused links * Change border size of package manager tabs * Delete comment line from base.less * Change nav-tabs color and make overflow-y visible for package-tags Co-authored-by: Joel Verhagen Co-authored-by: lyndaidaii <64443925+lyndaidaii@users.noreply.github.com> Co-authored-by: toseni Co-authored-by: Ian Rathbone Co-authored-by: Advay Tandon <82980589+advay26@users.noreply.github.com> Co-authored-by: Daniel Olczyk <44818681+MRmlik12@users.noreply.github.com> --- .../Configuration/GalleryConfiguration.cs | 1 + .../Providers/AccountDeleteUrlHelper.cs | 6 +- src/Bootstrap/dist/css/bootstrap-theme.css | 94 +++++++++++++ src/Bootstrap/less/theme/base.less | 18 +++ src/Bootstrap/less/theme/common-readme.less | 93 +++++++++++- .../less/theme/page-display-package.less | 12 +- .../theme/page-statistics-per-package.less | 9 +- src/GalleryTools/App.config | 1 + .../Configuration/AppConfiguration.cs | 5 + .../Configuration/ConfigurationService.cs | 56 ++++++-- .../Configuration/IAppConfiguration.cs | 5 + .../IGalleryConfigurationService.cs | 5 + .../NuGetGallery.Services.csproj | 12 +- .../PackageOwnershipManagementService.cs | 10 +- .../Providers/IUrlHelper.cs | 9 +- src/NuGetGallery/App_Code/ViewHelpers.cshtml | 1 - .../Admin/Controllers/ApiKeysController.cs | 4 +- src/NuGetGallery/Controllers/ApiController.cs | 3 + .../Controllers/AuthenticationController.cs | 3 +- .../Controllers/OrganizationsController.cs | 6 +- .../Controllers/UsersController.cs | 9 +- .../DisplayPackageViewModelFactory.cs | 1 + src/NuGetGallery/UrlHelperExtensions.cs | 132 +++++++++++------- src/NuGetGallery/UrlHelperWrapper.cs | 12 +- .../ViewModels/DisplayPackageViewModel.cs | 1 + .../Views/Packages/DisplayPackage.cshtml | 15 ++ .../Views/Statistics/_PivotTable.cshtml | 2 +- src/NuGetGallery/Web.config | 3 +- .../App_Start/ConfigurationServiceFacts.cs | 39 ++++++ .../AuthenticationControllerFacts.cs | 8 +- .../Controllers/UsersControllerFacts.cs | 2 +- .../Framework/UnitTestBindings.cs | 1 + .../TestUtils/TestUtility.cs | 1 + 33 files changed, 472 insertions(+), 107 deletions(-) diff --git a/src/AccountDeleter/Configuration/GalleryConfiguration.cs b/src/AccountDeleter/Configuration/GalleryConfiguration.cs index 11a99cb66e..d6a95bea8a 100644 --- a/src/AccountDeleter/Configuration/GalleryConfiguration.cs +++ b/src/AccountDeleter/Configuration/GalleryConfiguration.cs @@ -115,5 +115,6 @@ public string SiteRoot public int? MaxIoThreads { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public string InternalMicrosoftTenantKey { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public string AdminSenderUser { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public string SupportEmailSiteRoot { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } } } diff --git a/src/AccountDeleter/Providers/AccountDeleteUrlHelper.cs b/src/AccountDeleter/Providers/AccountDeleteUrlHelper.cs index d44ed96636..3624f1d84c 100644 --- a/src/AccountDeleter/Providers/AccountDeleteUrlHelper.cs +++ b/src/AccountDeleter/Providers/AccountDeleteUrlHelper.cs @@ -7,7 +7,7 @@ namespace NuGetGallery.AccountDeleter { public class AccountDeleteUrlHelper : IUrlHelper { - public string ConfirmPendingOwnershipRequest(string id, string username, string confirmationCode, bool relativeUrl) + public string ConfirmPendingOwnershipRequest(string id, string username, string confirmationCode, bool relativeUrl, bool supportEmail) { throw new NotImplementedException(); } @@ -17,12 +17,12 @@ public string ManagePackageOwnership(string id, bool relativeUrl) throw new NotImplementedException(); } - public string Package(string id, string version, bool relativeUrl) + public string Package(string id, string version, bool relativeUrl, bool supportEmail) { throw new NotImplementedException(); } - public string RejectPendingOwnershipRequest(string id, string username, string confirmationCode, bool relativeUrl) + public string RejectPendingOwnershipRequest(string id, string username, string confirmationCode, bool relativeUrl, bool supportEmail) { throw new NotImplementedException(); } diff --git a/src/Bootstrap/dist/css/bootstrap-theme.css b/src/Bootstrap/dist/css/bootstrap-theme.css index d7003afb38..f86a525476 100644 --- a/src/Bootstrap/dist/css/bootstrap-theme.css +++ b/src/Bootstrap/dist/css/bootstrap-theme.css @@ -95,6 +95,10 @@ body h3 { padding-bottom: 75px; height: auto; } +.main-container a[href]:focus { + outline: solid 2px; + outline-offset: 1px; +} .navbar-logo { margin: 8px 20px 0 0; } @@ -266,6 +270,10 @@ img.reserved-indicator-icon { list-style: none; display: list-item; } +.package-list li a:focus { + outline: solid 0.25px; + outline-offset: 0; +} @media (min-width: 768px) { .package-list li { display: inline-block; @@ -274,6 +282,9 @@ img.reserved-indicator-icon { overflow-y: hidden; padding-right: 10px; } + .package-list li.package-tags { + overflow-y: visible; + } } .package-title-text { margin-bottom: 0; @@ -690,6 +701,78 @@ img.reserved-indicator-icon { } .readme-common pre code.hljs { background-color: #f6f8fa; + color: #24292e; +} +.readme-common pre code.hljs .hljs-doctag, +.readme-common pre code.hljs .hljs-keyword, +.readme-common pre code.hljs .hljs-meta .hljs-keyword, +.readme-common pre code.hljs .hljs-template-tag, +.readme-common pre code.hljs .hljs-template-variable, +.readme-common pre code.hljs .hljs-type, +.readme-common pre code.hljs .hljs-variable.language_ .hljs-built_in { + color: #cc3745; +} +.readme-common pre code.hljs .hljs-title, +.readme-common pre code.hljs .hljs-title.class_, +.readme-common pre code.hljs .hljs-title.class_.inherited__, +.readme-common pre code.hljs .hljs-title.function_ { + color: #6f42c1; +} +.readme-common pre code.hljs .hljs-attr, +.readme-common pre code.hljs .hljs-attribute, +.readme-common pre code.hljs .hljs-literal, +.readme-common pre code.hljs .hljs-meta, +.readme-common pre code.hljs .hljs-number, +.readme-common pre code.hljs .hljs-operator, +.readme-common pre code.hljs .hljs-variable, +.readme-common pre code.hljs .hljs-selector-attr, +.readme-common pre code.hljs .hljs-selector-class, +.readme-common pre code.hljs .hljs-selector-id { + color: #005cc5; +} +.readme-common pre code.hljs .hljs-regexp, +.readme-common pre code.hljs .hljs-string, +.readme-common pre code.hljs .hljs-meta .hljs-string { + color: #032f62; +} +.readme-common pre code.hljs .hljs-built_in, +.readme-common pre code.hljs .hljs-symbol { + color: #b74e05; +} +.readme-common pre code.hljs .hljs-comment, +.readme-common pre code.hljs .hljs-code, +.readme-common pre code.hljs .hljs-formula { + color: #6a737d; +} +.readme-common pre code.hljs .hljs-name, +.readme-common pre code.hljs .hljs-quote, +.readme-common pre code.hljs .hljs-selector-tag, +.readme-common pre code.hljs .hljs-selector-pseudo { + color: #207f37; +} +.readme-common pre code.hljs .hljs-subst { + color: #24292e; +} +.readme-common pre code.hljs .hljs-section { + color: #005cc5; + font-weight: bold; +} +.readme-common pre code.hljs .hljs-bullet { + color: #735c0f; +} +.readme-common pre code.hljs .hljs-emphasis { + color: #24292e; + font-style: italic; +} +.readme-common pre code.hljs .hljs-strong { + color: #24292e; + font-weight: bold; +} +.readme-common pre code.hljs .hljs-addition { + color: #207f37; +} +.readme-common pre code.hljs .hljs-deletion { + color: #b31d28; } #readme-preview { padding-top: 0.25em; @@ -1465,6 +1548,10 @@ p.frameworktableinfo-text { padding: 5px 10px; margin: 0; } +.page-package-details .install-tabs .nav-tabs > li > a:focus { + outline: 3px solid #0078D4; + outline-offset: 0; +} .page-package-details .install-tabs .nav-tabs > li.active > a { background-color: #002440; border: 0; @@ -1519,6 +1606,10 @@ p.frameworktableinfo-text { font-family: 'Segoe UI', "Helvetica Neue", Helvetica, Arial, sans-serif; color: #323130; } +.page-package-details .body-tabs .nav-tabs > li > a:focus { + outline: 3px solid #0078D4; + outline-offset: 0; +} .page-package-details .body-tabs .nav-tabs > li > a .ms-Icon { position: relative; top: 2px; @@ -2172,6 +2263,9 @@ p.frameworktableinfo-text { .page-statistics-overview text.graph-y-axis-text { font-size: 0.9em; } +.page-stats-per-package .table { + table-layout: fixed; +} .page-stats-per-package .stats-table-control { margin-top: 40px; } diff --git a/src/Bootstrap/less/theme/base.less b/src/Bootstrap/less/theme/base.less index 43292152f5..6daa2fb05e 100644 --- a/src/Bootstrap/less/theme/base.less +++ b/src/Bootstrap/less/theme/base.less @@ -118,6 +118,13 @@ body { .main-container { padding-bottom: 75px; height: auto; + + a[href] { + &:focus { + outline: solid 2px; + outline-offset: 1px; + } + } } .navbar-logo { @@ -343,6 +350,13 @@ img.reserved-indicator-icon { li { list-style: none; display: list-item; + + a { + &:focus { + outline: solid 0.25px; + outline-offset: 0; + } + } } @media (min-width: @screen-sm-min) { @@ -353,6 +367,10 @@ img.reserved-indicator-icon { overflow-y: hidden; padding-right: @padding-small-horizontal; } + + li.package-tags { + overflow-y: visible; + } } } diff --git a/src/Bootstrap/less/theme/common-readme.less b/src/Bootstrap/less/theme/common-readme.less index 3fb33303bc..8b08671064 100644 --- a/src/Bootstrap/less/theme/common-readme.less +++ b/src/Bootstrap/less/theme/common-readme.less @@ -42,8 +42,97 @@ font-size: @font-size-base; } - pre code.hljs{ - background-color: #f6f8fa; + pre { + code.hljs { + background-color: #f6f8fa; + color: #24292e; + + .hljs-doctag, + .hljs-keyword, + .hljs-meta .hljs-keyword, + .hljs-template-tag, + .hljs-template-variable, + .hljs-type, + .hljs-variable.language_ + .hljs-built_in { + color: #cc3745; + } + + .hljs-title, + .hljs-title.class_, + .hljs-title.class_.inherited__, + .hljs-title.function_ { + color: #6f42c1; + } + + .hljs-attr, + .hljs-attribute, + .hljs-literal, + .hljs-meta, + .hljs-number, + .hljs-operator, + .hljs-variable, + .hljs-selector-attr, + .hljs-selector-class, + .hljs-selector-id { + color: #005cc5; + } + + .hljs-regexp, + .hljs-string, + .hljs-meta .hljs-string { + color: #032f62; + } + + .hljs-built_in, + .hljs-symbol { + color: #b74e05; + } + + .hljs-comment, + .hljs-code, + .hljs-formula { + color: #6a737d; + } + + .hljs-name, + .hljs-quote, + .hljs-selector-tag, + .hljs-selector-pseudo { + color: #207f37; + } + + .hljs-subst { + color: #24292e; + } + + .hljs-section { + color: #005cc5; + font-weight: bold; + } + + .hljs-bullet { + color: #735c0f; + } + + .hljs-emphasis { + color: #24292e; + font-style: italic; + } + + .hljs-strong { + color: #24292e; + font-weight: bold; + } + + .hljs-addition { + color: #207f37; + } + + .hljs-deletion { + color: #b31d28; + } + } } } diff --git a/src/Bootstrap/less/theme/page-display-package.less b/src/Bootstrap/less/theme/page-display-package.less index fb8aef088d..5deac91aea 100644 --- a/src/Bootstrap/less/theme/page-display-package.less +++ b/src/Bootstrap/less/theme/page-display-package.less @@ -357,6 +357,11 @@ border: 0; padding: 5px 10px; margin: 0; + + &:focus { + outline: 3px solid #0078D4; + outline-offset: 0; + } } } } @@ -371,7 +376,7 @@ .tab-content { .tab-pane { - + .install-script-row { display: flex; height: 100%; @@ -426,6 +431,11 @@ font-family: @font-family-base; color: #323130; + &:focus { + outline: 3px solid #0078D4; + outline-offset: 0; + } + .ms-Icon { position: relative; top: 2px; diff --git a/src/Bootstrap/less/theme/page-statistics-per-package.less b/src/Bootstrap/less/theme/page-statistics-per-package.less index 16cf4e3555..bb4da4ace8 100644 --- a/src/Bootstrap/less/theme/page-statistics-per-package.less +++ b/src/Bootstrap/less/theme/page-statistics-per-package.less @@ -1,5 +1,8 @@ -.page-stats-per-package -{ +.page-stats-per-package { + .table { + table-layout: fixed; + } + .stats-table-control { margin-top: 40px; } @@ -76,4 +79,4 @@ .statistics-report-title { overflow-wrap: break-word; } -} \ No newline at end of file +} diff --git a/src/GalleryTools/App.config b/src/GalleryTools/App.config index 11892f2d4e..b1dc6b78f3 100644 --- a/src/GalleryTools/App.config +++ b/src/GalleryTools/App.config @@ -25,6 +25,7 @@ + diff --git a/src/NuGetGallery.Services/Configuration/AppConfiguration.cs b/src/NuGetGallery.Services/Configuration/AppConfiguration.cs index a03ee49846..2eef4c2632 100644 --- a/src/NuGetGallery.Services/Configuration/AppConfiguration.cs +++ b/src/NuGetGallery.Services/Configuration/AppConfiguration.cs @@ -210,6 +210,11 @@ public class AppConfiguration : IAppConfiguration /// public string SiteRoot { get; set; } + /// + /// Gets the protocol-independent support email site root + /// + public string SupportEmailSiteRoot { get; set; } + /// /// Private key for verifying recaptcha user response. /// diff --git a/src/NuGetGallery.Services/Configuration/ConfigurationService.cs b/src/NuGetGallery.Services/Configuration/ConfigurationService.cs index d55706f978..fba91fe698 100644 --- a/src/NuGetGallery.Services/Configuration/ConfigurationService.cs +++ b/src/NuGetGallery.Services/Configuration/ConfigurationService.cs @@ -26,6 +26,7 @@ public class ConfigurationService : IGalleryConfigurationService, IConfiguration private readonly Lazy _httpSiteRootThunk; private readonly Lazy _httpsSiteRootThunk; + private readonly Lazy _httpsEmailSupportSiteRootThunk; private readonly Lazy _lazyAppConfiguration; private readonly Lazy _lazyFeatureConfiguration; private readonly Lazy _lazyServiceBusConfiguration; @@ -59,6 +60,7 @@ public ConfigurationService() { _httpSiteRootThunk = new Lazy(GetHttpSiteRoot); _httpsSiteRootThunk = new Lazy(GetHttpsSiteRoot); + _httpsEmailSupportSiteRootThunk = new Lazy(GetHttpsSupportEmailSiteRoot); _lazyAppConfiguration = new Lazy(() => ResolveSettings().Result); _lazyFeatureConfiguration = new Lazy(() => ResolveFeatures().Result); @@ -89,6 +91,15 @@ public string GetSiteRoot(bool useHttps) return useHttps ? _httpsSiteRootThunk.Value : _httpSiteRootThunk.Value; } + /// + /// Gets the support email site root using the specified protocol + /// + /// + public string GetSupportEmailSiteRoot() + { + return _httpsEmailSupportSiteRootThunk.Value; + } + public Task Get() where T : NuGet.Services.Configuration.Configuration, new() { // Get the prefix specified by the ConfigurationKeyPrefixAttribute on the class if it exists. @@ -209,19 +220,7 @@ private string GetHttpSiteRoot() { var siteRoot = Current.SiteRoot; - if (siteRoot == null) - { - // No SiteRoot configured in settings. - // Fallback to detected site root. - var request = GetCurrentRequest(); - siteRoot = request.Url.GetLeftPart(UriPartial.Authority) + '/'; - } - - if (!siteRoot.StartsWith("http://", StringComparison.OrdinalIgnoreCase) - && !siteRoot.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidOperationException("The configured site root must start with either http:// or https://."); - } + CheckValidSiteRoot(siteRoot); if (siteRoot.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { @@ -242,5 +241,36 @@ private string GetHttpsSiteRoot() return "https://" + siteRoot.Substring(7); } + + private string GetHttpsSupportEmailSiteRoot() + { + var siteRoot = Current.SupportEmailSiteRoot; + + CheckValidSiteRoot(siteRoot); + + if (siteRoot.StartsWith("http://", StringComparison.OrdinalIgnoreCase)) + { + siteRoot = "https://" + siteRoot.Substring(7); + } + + return siteRoot; + } + + private void CheckValidSiteRoot(string siteRoot) + { + if (siteRoot == null) + { + // No SiteRoot configured in settings. + // Fallback to detected site root. + var request = GetCurrentRequest(); + siteRoot = request.Url.GetLeftPart(UriPartial.Authority) + '/'; + } + + if (!siteRoot.StartsWith("http://", StringComparison.OrdinalIgnoreCase) + && !siteRoot.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("The configured site root must start with either http:// or https://."); + } + } } } \ No newline at end of file diff --git a/src/NuGetGallery.Services/Configuration/IAppConfiguration.cs b/src/NuGetGallery.Services/Configuration/IAppConfiguration.cs index 0e30dbd6bb..01f0219939 100644 --- a/src/NuGetGallery.Services/Configuration/IAppConfiguration.cs +++ b/src/NuGetGallery.Services/Configuration/IAppConfiguration.cs @@ -231,6 +231,11 @@ public interface IAppConfiguration : IMessageServiceConfiguration /// string SiteRoot { get; set; } + /// + /// Gets the protocol-independent support email site root + /// + string SupportEmailSiteRoot { get; set; } + /// /// Private key for verifying recaptcha user response. /// diff --git a/src/NuGetGallery.Services/Configuration/IGalleryConfigurationService.cs b/src/NuGetGallery.Services/Configuration/IGalleryConfigurationService.cs index 07260407e2..8dbc1b68bc 100644 --- a/src/NuGetGallery.Services/Configuration/IGalleryConfigurationService.cs +++ b/src/NuGetGallery.Services/Configuration/IGalleryConfigurationService.cs @@ -17,6 +17,11 @@ public interface IGalleryConfigurationService /// If true, the root will be returned in HTTPS form, otherwise, HTTP. string GetSiteRoot(bool useHttps); + /// + /// Gets the support email site root using the specified protocol + /// + string GetSupportEmailSiteRoot(); + /// /// Populate the properties of from configuration. /// diff --git a/src/NuGetGallery.Services/NuGetGallery.Services.csproj b/src/NuGetGallery.Services/NuGetGallery.Services.csproj index 5990654792..d32dc98cdd 100644 --- a/src/NuGetGallery.Services/NuGetGallery.Services.csproj +++ b/src/NuGetGallery.Services/NuGetGallery.Services.csproj @@ -67,22 +67,22 @@ 1.0.0 - 4.1.0 + 4.2.2 - 4.1.0 + 4.2.2 - 4.1.0 + 4.2.2 - 4.1.0 + 4.2.2 - 4.1.0 + 4.2.2 - 4.1.0 + 4.2.2 6.0.0 diff --git a/src/NuGetGallery.Services/PackageManagement/PackageOwnershipManagementService.cs b/src/NuGetGallery.Services/PackageManagement/PackageOwnershipManagementService.cs index 3ea8e4f596..9c27e6d971 100644 --- a/src/NuGetGallery.Services/PackageManagement/PackageOwnershipManagementService.cs +++ b/src/NuGetGallery.Services/PackageManagement/PackageOwnershipManagementService.cs @@ -49,7 +49,7 @@ public async Task AddPackageOwnerWithMessagesAsync(PackageRegistration packageRe { await AddPackageOwnerAsync(packageRegistration, user, commitChanges: true); - var packageUrl = _urlHelper.Package(packageRegistration.Id, version: null, relativeUrl: false); + var packageUrl = _urlHelper.Package(packageRegistration.Id, version: null, relativeUrl: false, supportEmail: true); // Accumulate the tasks so that they are sent in parallel and as many messages as possible are sent even if // one fails (i.e. throws an exception). @@ -155,7 +155,7 @@ public async Task AddPackageOwnershipRequestWithMessagesAsy var encodedMessage = HttpUtility.HtmlEncode(message ?? string.Empty); - var packageUrl = _urlHelper.Package(packageRegistration.Id, version: null, relativeUrl: false); + var packageUrl = _urlHelper.Package(packageRegistration.Id, version: null, relativeUrl: false, supportEmail: true); var ownerRequest = await AddPackageOwnershipRequestAsync( packageRegistration, requestingOwner, newOwner); @@ -164,13 +164,15 @@ public async Task AddPackageOwnershipRequestWithMessagesAsy packageRegistration.Id, newOwner.Username, ownerRequest.ConfirmationCode, - relativeUrl: false); + relativeUrl: false, + supportEmail: true); var rejectionUrl = _urlHelper.RejectPendingOwnershipRequest( packageRegistration.Id, newOwner.Username, ownerRequest.ConfirmationCode, - relativeUrl: false); + relativeUrl: false, + supportEmail: true); var manageUrl = _urlHelper.ManagePackageOwnership( packageRegistration.Id, diff --git a/src/NuGetGallery.Services/Providers/IUrlHelper.cs b/src/NuGetGallery.Services/Providers/IUrlHelper.cs index 1bc927fb49..3894eb63df 100644 --- a/src/NuGetGallery.Services/Providers/IUrlHelper.cs +++ b/src/NuGetGallery.Services/Providers/IUrlHelper.cs @@ -17,8 +17,9 @@ public interface IUrlHelper /// The package ID to link to. /// The specific package version to link to. Can be null. /// True to return a relative URL, false to return an absolute URL. + /// True to return a supportEmail root site URL, false to return an root site URL. /// The relative or absolute URL as a string. - string Package(string id, string version, bool relativeUrl); + string Package(string id, string version, bool relativeUrl, bool supportEmail); /// /// Produces a URL to the package ownership request confirmation page. @@ -27,8 +28,9 @@ public interface IUrlHelper /// The username of the ownership request recipient (new owner). /// The confirmation code (secret) associated with the request. /// True to return a relative URL, false to return an absolute URL. + /// True to return a supportEmail root site URL, false to return an root site URL. /// The relative or absolute URL as a string. - string ConfirmPendingOwnershipRequest(string id, string username, string confirmationCode, bool relativeUrl); + string ConfirmPendingOwnershipRequest(string id, string username, string confirmationCode, bool relativeUrl, bool supportEmail); /// /// Produces a URL to the package ownership request rejection page. @@ -37,8 +39,9 @@ public interface IUrlHelper /// The username of the ownership request recipient (new owner). /// The confirmation code (secret) associated with the request. /// True to return a relative URL, false to return an absolute URL. + /// True to return a supportEmail root site URL, false to return an root site URL. /// The relative or absolute URL as a string. - string RejectPendingOwnershipRequest(string id, string username, string confirmationCode, bool relativeUrl); + string RejectPendingOwnershipRequest(string id, string username, string confirmationCode, bool relativeUrl, bool supportEmail); /// /// Produces a URL to manage the ownership of an existing package. diff --git a/src/NuGetGallery/App_Code/ViewHelpers.cshtml b/src/NuGetGallery/App_Code/ViewHelpers.cshtml index d0999b3efd..7f827e90cd 100644 --- a/src/NuGetGallery/App_Code/ViewHelpers.cshtml +++ b/src/NuGetGallery/App_Code/ViewHelpers.cshtml @@ -734,7 +734,6 @@ var hlp = new AccordionHelper(name, formModelStatePrefix, expanded, page); @helper IncludeSyntaxHighlightScript() { - @* highlight.js build includes support for:: bash, c, cpp, csharp, css, diff, go, ini, java, json, javascript, typescript, kotlin, less, lua, makefile, xml, markdown, perl, php, objectivec, plaintext, python, r, ruby,rust, scss, shell, sql, swift, vbnet, yaml, html, fsharp, powershell, dos*@ diff --git a/src/NuGetGallery/Areas/Admin/Controllers/ApiKeysController.cs b/src/NuGetGallery/Areas/Admin/Controllers/ApiKeysController.cs index 24bdd779dc..b90d4b3645 100644 --- a/src/NuGetGallery/Areas/Admin/Controllers/ApiKeysController.cs +++ b/src/NuGetGallery/Areas/Admin/Controllers/ApiKeysController.cs @@ -130,8 +130,8 @@ public async Task Revoke(RevokeApiKeysRequest revokeApiKeysRequest credential: apiKeyCredential, leakedUrl: apiKeyInfo.LeakedUrl, revocationSource: apiKeyInfo.RevocationSource, - manageApiKeyUrl: Url.ManageMyApiKeys(relativeUrl: false), - contactUrl: Url.Contact(relativeUrl: false)); + manageApiKeyUrl: Url.ManageMyApiKeys(relativeUrl: false, supportEmail: true), + contactUrl: Url.Contact(relativeUrl: false, supportEmail: true)); await _messageService.SendMessageAsync(credentialRevokedMessage); await _authenticationService.RevokeApiKeyCredential(apiKeyCredential, revocationSourceKey, commitChanges: false); diff --git a/src/NuGetGallery/Controllers/ApiController.cs b/src/NuGetGallery/Controllers/ApiController.cs index bb39cae9f2..2ccd776adf 100644 --- a/src/NuGetGallery/Controllers/ApiController.cs +++ b/src/NuGetGallery/Controllers/ApiController.cs @@ -331,6 +331,7 @@ public virtual ActionResult SimulateError(SimulatedErrorType type = SimulatedErr [ApiScopeRequired(NuGetScopes.PackagePush, NuGetScopes.PackagePushVersion)] [ActionName("CreatePackageVerificationKey")] public virtual async Task CreatePackageVerificationKeyAsync(string id, string version) + // CodeQL [SM00433] This endpoint uses API Key authentication { // For backwards compatibility, we must preserve existing behavior where the client always pushes // symbols and the VerifyPackageKey callback returns the appropriate response. For this reason, we @@ -427,6 +428,7 @@ public virtual Task CreatePackagePut() [ApiScopeRequired(NuGetScopes.PackagePush, NuGetScopes.PackagePushVersion)] [ActionName("PushPackageApi")] public virtual Task CreatePackagePost() + // CodeQL [SM00433] This endpoint uses API Key authentication { return CreatePackageInternal(); } @@ -948,6 +950,7 @@ await PackageDeleteService.SoftDeletePackagesAsync( [ApiScopeRequired(NuGetScopes.PackageUnlist)] [ActionName("PublishPackageApi")] public virtual async Task PublishPackage(string id, string version) + // CodeQL [SM00433] This endpoint uses API Key authentication { var package = PackageService.FindPackageByIdAndVersionStrict(id, version); if (package == null) diff --git a/src/NuGetGallery/Controllers/AuthenticationController.cs b/src/NuGetGallery/Controllers/AuthenticationController.cs index 5b6a633392..9bc921256a 100644 --- a/src/NuGetGallery/Controllers/AuthenticationController.cs +++ b/src/NuGetGallery/Controllers/AuthenticationController.cs @@ -330,7 +330,8 @@ public virtual async Task Register(LogOnViewModel model, string re Url.ConfirmEmail( user.User.Username, user.User.EmailConfirmationToken, - relativeUrl: false)); + relativeUrl: false, + supportEmail: true)); await _messageService.SendMessageAsync(message); } diff --git a/src/NuGetGallery/Controllers/OrganizationsController.cs b/src/NuGetGallery/Controllers/OrganizationsController.cs index 817714c0db..ae2ddc694b 100644 --- a/src/NuGetGallery/Controllers/OrganizationsController.cs +++ b/src/NuGetGallery/Controllers/OrganizationsController.cs @@ -152,9 +152,9 @@ public async Task AddMember(string accountName, string memberName, b request.NewMember, currentUser, request.IsAdmin, - profileUrl: Url.User(account, relativeUrl: false), - confirmationUrl: Url.AcceptOrganizationMembershipRequest(request, relativeUrl: false), - rejectionUrl: Url.RejectOrganizationMembershipRequest(request, relativeUrl: false)); + profileUrl: Url.User(account, relativeUrl: false, supportEmail: true), + confirmationUrl: Url.AcceptOrganizationMembershipRequest(request, relativeUrl: false, supportEmail: true), + rejectionUrl: Url.RejectOrganizationMembershipRequest(request, relativeUrl: false, supportEmail: true)); await MessageService.SendMessageAsync(organizationMembershipRequestMessage); var organizationMembershipRequestInitiatedMessage = new OrganizationMembershipRequestInitiatedMessage( diff --git a/src/NuGetGallery/Controllers/UsersController.cs b/src/NuGetGallery/Controllers/UsersController.cs index b4bc42c76c..4a5e0f7369 100644 --- a/src/NuGetGallery/Controllers/UsersController.cs +++ b/src/NuGetGallery/Controllers/UsersController.cs @@ -203,9 +203,9 @@ public virtual async Task TransformToOrganization(TransformAccount _config, accountToTransform, adminUser, - profileUrl: Url.User(accountToTransform, relativeUrl: false), - confirmationUrl: Url.ConfirmTransformAccount(accountToTransform, relativeUrl: false), - rejectionUrl: Url.RejectTransformAccount(accountToTransform, relativeUrl: false)); + profileUrl: Url.User(accountToTransform, relativeUrl: false, supportEmail: true), + confirmationUrl: Url.ConfirmTransformAccount(accountToTransform, relativeUrl: false, supportEmail: true), + rejectionUrl: Url.RejectTransformAccount(accountToTransform, relativeUrl: false, supportEmail: true)); await MessageService.SendMessageAsync(organizationTransformRequestMessage); var organizationTransformInitiatedMessage = new OrganizationTransformInitiatedMessage( @@ -1257,7 +1257,8 @@ private async Task SendPasswordResetEmailAsync(User user, bool for user.Username, user.PasswordResetToken, forgotPassword, - relativeUrl: false); + relativeUrl: false, + supportEmail: true); var message = new PasswordResetInstructionsMessage( MessageServiceConfiguration, diff --git a/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs b/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs index b540c52b7b..adbcf78989 100644 --- a/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs +++ b/src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs @@ -104,6 +104,7 @@ private DisplayPackageViewModel SetupInternal( // Lazily load the package types from the database. viewModel.IsDotnetToolPackageType = package.PackageTypes.Any(e => e.Name.Equals("DotnetTool", StringComparison.OrdinalIgnoreCase)); viewModel.IsDotnetNewTemplatePackageType = package.PackageTypes.Any(e => e.Name.Equals("Template", StringComparison.OrdinalIgnoreCase)); + viewModel.IsMSBuildSdkPackageType = package.PackageTypes.Any(e => e.Name.Equals("MSBuildSdk", StringComparison.OrdinalIgnoreCase)); } if (packageKeyToDeprecation != null && packageKeyToDeprecation.TryGetValue(package.Key, out var deprecation)) diff --git a/src/NuGetGallery/UrlHelperExtensions.cs b/src/NuGetGallery/UrlHelperExtensions.cs index fd49b5d555..eb8c36227f 100644 --- a/src/NuGetGallery/UrlHelperExtensions.cs +++ b/src/NuGetGallery/UrlHelperExtensions.cs @@ -8,6 +8,7 @@ using System.Web.Mvc; using System.Web.Routing; using NuGet.Services.Entities; +using NuGet.Services.Logging; using NuGetGallery.Areas.Admin; using NuGetGallery.Areas.Admin.Controllers; using NuGetGallery.Configuration; @@ -68,6 +69,11 @@ internal static string GetSiteRoot(bool useHttps) return _configuration.GetSiteRoot(useHttps); } + internal static string GetSupportEmailSiteRoot(bool useHttps) + { + return _configuration.GetSupportEmailSiteRoot(); + } + public static string GetCanonicalLinkUrl(this UrlHelper url) { var current = url.RequestContext.HttpContext.Request.Url; @@ -89,6 +95,12 @@ private static string GetConfiguredSiteHostName() return new Uri(siteRoot).Host; } + private static string GetConfiguredSupportEmailSiteHostName() + { + var siteRoot = GetSupportEmailSiteRoot(useHttps: true); + return new Uri(siteRoot).Host; + } + public static string Home(this UrlHelper url, bool relativeUrl = true) { return GetRouteLink(url, RouteName.Home, relativeUrl); @@ -228,9 +240,9 @@ public static RouteUrlTemplate PackageRegistrationTemplate return new RouteUrlTemplate(linkGenerator, routesGenerator); } - public static string Package(this UrlHelper url, string id, bool relativeUrl = true) + public static string Package(this UrlHelper url, string id, bool relativeUrl = true, bool supportEmail = false) { - return url.Package(id, version: null, relativeUrl: relativeUrl); + return url.Package(id, version: null, relativeUrl: relativeUrl, supportEmail: supportEmail); } public static string Package( @@ -238,7 +250,8 @@ public static string Package( string id, string version, bool relativeUrl = true, - bool preview = false) + bool preview = false, + bool supportEmail = false) { var normalized = (version != null) ? NuGetVersionFormatter.Normalize(version) : version; @@ -251,7 +264,8 @@ public static string Package( { "id", id }, { "version", normalized }, { "preview", preview ? "1" : null } - }); + }, + supportEmail: supportEmail); // Ensure trailing slashes for versionless package URLs, as a fix for package filenames that look like known file extensions return version == null ? EnsureTrailingSlash(result) : result; @@ -592,7 +606,8 @@ public static string User( this UrlHelper url, User user, int page = 1, - bool relativeUrl = true) + bool relativeUrl = true, + bool supportEmail = false) { var routeValues = new RouteValueDictionary { @@ -604,7 +619,7 @@ public static string User( routeValues.Add("page", page); } - return GetActionLink(url, "Profiles", "Users", relativeUrl, routeValues); + return GetActionLink(url, "Profiles", "Users", relativeUrl, routeValues, supportEmail: supportEmail); } public static string Avatar( @@ -965,13 +980,14 @@ private static string GetAuthenticationRoute(this UrlHelper url, string action, interceptReturnUrl: false); } - public static string ManageMyApiKeys(this UrlHelper url, bool relativeUrl = true) + public static string ManageMyApiKeys(this UrlHelper url, bool relativeUrl = true, bool supportEmail = false) { return GetActionLink( url, nameof(UsersController.ApiKeys), "Users", - relativeUrl); + relativeUrl, + supportEmail: supportEmail); } public static string ManageMyOrganizations(this UrlHelper url, bool relativeUrl = true) @@ -1016,35 +1032,37 @@ public static string AddOrganizationMember(this UrlHelper url, string accountNam }); } - public static string AcceptOrganizationMembershipRequest(this UrlHelper url, MembershipRequest request, bool relativeUrl = true) + public static string AcceptOrganizationMembershipRequest(this UrlHelper url, MembershipRequest request, bool relativeUrl = true, bool supportEmail = false) { - return url.AcceptOrganizationMembershipRequest(request.Organization.Username, request.ConfirmationToken, relativeUrl); + return url.AcceptOrganizationMembershipRequest(request.Organization.Username, request.ConfirmationToken, relativeUrl, supportEmail); } - public static string RejectOrganizationMembershipRequest(this UrlHelper url, MembershipRequest request, bool relativeUrl = true) + public static string RejectOrganizationMembershipRequest(this UrlHelper url, MembershipRequest request, bool relativeUrl = true, bool supportEmail = false) { - return url.RejectOrganizationMembershipRequest(request.Organization.Username, request.ConfirmationToken, relativeUrl); + return url.RejectOrganizationMembershipRequest(request.Organization.Username, request.ConfirmationToken, relativeUrl, supportEmail); } - public static string AcceptOrganizationMembershipRequest(this UrlHelper url, string organizationUsername, string confirmationToken, bool relativeUrl = true) + public static string AcceptOrganizationMembershipRequest(this UrlHelper url, string organizationUsername, string confirmationToken, bool relativeUrl = true, bool supportEmail = false) { return url.HandleOrganizationMembershipRequest( nameof(OrganizationsController.ConfirmMemberRequest), organizationUsername, confirmationToken, - relativeUrl); + relativeUrl, + supportEmail); } - public static string RejectOrganizationMembershipRequest(this UrlHelper url, string organizationUsername, string confirmationToken, bool relativeUrl = true) + public static string RejectOrganizationMembershipRequest(this UrlHelper url, string organizationUsername, string confirmationToken, bool relativeUrl = true, bool supportEmail = false) { return url.HandleOrganizationMembershipRequest( nameof(OrganizationsController.RejectMemberRequest), organizationUsername, confirmationToken, - relativeUrl); + relativeUrl, + supportEmail); } - private static string HandleOrganizationMembershipRequest(this UrlHelper url, string actionName, string organizationUsername, string confirmationToken, bool relativeUrl = true) + private static string HandleOrganizationMembershipRequest(this UrlHelper url, string actionName, string organizationUsername, string confirmationToken, bool relativeUrl = true, bool supportEmail = false) { return GetActionLink(url, actionName, @@ -1054,7 +1072,8 @@ private static string HandleOrganizationMembershipRequest(this UrlHelper url, st { { "accountName", organizationUsername }, { "confirmationToken", confirmationToken } - }); + }, + supportEmail: supportEmail); } public static string CancelOrganizationMembershipRequest(this UrlHelper url, string accountName, bool relativeUrl = true) @@ -1199,7 +1218,8 @@ public static string ConfirmPendingOwnershipRequest( string packageId, string username, string confirmationCode, - bool relativeUrl = true) + bool relativeUrl = true, + bool supportEmail = false) { return HandlePendingOwnershipRequest( url, @@ -1207,7 +1227,8 @@ public static string ConfirmPendingOwnershipRequest( packageId, username, confirmationCode, - relativeUrl); + relativeUrl, + supportEmail); } public static RouteUrlTemplate RejectPendingOwnershipRequestTemplate( @@ -1225,7 +1246,8 @@ public static string RejectPendingOwnershipRequest( string packageId, string username, string confirmationCode, - bool relativeUrl = true) + bool relativeUrl = true, + bool supportEmail = false) { return HandlePendingOwnershipRequest( url, @@ -1233,7 +1255,8 @@ public static string RejectPendingOwnershipRequest( packageId, username, confirmationCode, - relativeUrl); + relativeUrl, + supportEmail); } private static RouteUrlTemplate HandlePendingOwnershipRequestTemplate( @@ -1264,7 +1287,8 @@ private static string HandlePendingOwnershipRequest( string packageId, string username, string confirmationCode, - bool relativeUrl = true) + bool relativeUrl = true, + bool supportEmail = false) { var routeValues = new RouteValueDictionary { @@ -1273,14 +1297,15 @@ private static string HandlePendingOwnershipRequest( ["token"] = confirmationCode }; - return GetActionLink(url, actionName, "Packages", relativeUrl, routeValues); + return GetActionLink(url, actionName, "Packages", relativeUrl, routeValues, supportEmail: supportEmail); } public static string ConfirmEmail( this UrlHelper url, string username, string token, - bool relativeUrl = true) + bool relativeUrl = true, + bool supportEmail = false) { var routeValues = new RouteValueDictionary { @@ -1288,14 +1313,15 @@ public static string ConfirmEmail( ["token"] = token }; - return GetActionLink(url, "Confirm", "Users", relativeUrl, routeValues); + return GetActionLink(url, "Confirm", "Users", relativeUrl, routeValues, supportEmail: supportEmail); } public static string ConfirmOrganizationEmail( this UrlHelper url, string username, string token, - bool relativeUrl = true) + bool relativeUrl = true, + bool supportEmail = false) { var routeValues = new RouteValueDictionary { @@ -1303,7 +1329,7 @@ public static string ConfirmOrganizationEmail( ["token"] = token }; - return GetActionLink(url, "Confirm", "Organizations", relativeUrl, routeValues); + return GetActionLink(url, "Confirm", "Organizations", relativeUrl, routeValues, supportEmail: supportEmail); } public static string ResetEmailOrPassword( @@ -1311,7 +1337,8 @@ public static string ResetEmailOrPassword( string username, string token, bool forgotPassword, - bool relativeUrl = true) + bool relativeUrl = true, + bool supportEmail = false) { var routeValues = new RouteValueDictionary { @@ -1320,7 +1347,7 @@ public static string ResetEmailOrPassword( ["forgot"] = forgotPassword }; - return GetActionLink(url, "ResetPassword", "Users", relativeUrl, routeValues); + return GetActionLink(url, "ResetPassword", "Users", relativeUrl, routeValues, supportEmail: supportEmail); } public static string VerifyPackage(this UrlHelper url, bool relativeUrl = true) @@ -1338,9 +1365,9 @@ public static string Downloads(this UrlHelper url, bool relativeUrl = true) return GetRouteLink(url, RouteName.Downloads, relativeUrl); } - public static string Contact(this UrlHelper url, bool relativeUrl = true) + public static string Contact(this UrlHelper url, bool relativeUrl = true, bool supportEmail = false) { - return GetActionLink(url, "Contact", "Pages", relativeUrl); + return GetActionLink(url, "Contact", "Pages", relativeUrl, supportEmail: supportEmail); } public static string ContactOwners(this UrlHelper url, IPackageVersionModel package, bool relativeUrl = true) @@ -1442,50 +1469,55 @@ public static string TransformAccount(this UrlHelper url, bool relativeUrl = tru relativeUrl); } - public static string ConfirmTransformAccount(this UrlHelper url, User accountToTransform, bool relativeUrl = true) + public static string ConfirmTransformAccount(this UrlHelper url, User accountToTransform, bool relativeUrl = true, bool supportEmail = false) { return url.HandleTransformAccount( nameof(UsersController.ConfirmTransformToOrganization), accountToTransform, - relativeUrl); + relativeUrl, + supportEmail); } - public static string RejectTransformAccount(this UrlHelper url, User accountToTransform, bool relativeUrl = true) + public static string RejectTransformAccount(this UrlHelper url, User accountToTransform, bool relativeUrl = true, bool supportEmail = false) { return url.HandleTransformAccount( nameof(UsersController.RejectTransformToOrganization), accountToTransform, - relativeUrl); + relativeUrl, + supportEmail); } - private static string HandleTransformAccount(this UrlHelper url, string action, User accountToTransform, bool relativeUrl = true) + private static string HandleTransformAccount(this UrlHelper url, string action, User accountToTransform, bool relativeUrl = true, bool supportEmail = false) { return url.HandleTransformAccount( action, accountToTransform.Username, accountToTransform.OrganizationMigrationRequest.ConfirmationToken, - relativeUrl); + relativeUrl, + supportEmail); } - public static string ConfirmTransformAccount(this UrlHelper url, string accountToTransformUsername, string confirmationToken, bool relativeUrl = true) + public static string ConfirmTransformAccount(this UrlHelper url, string accountToTransformUsername, string confirmationToken, bool relativeUrl = true, bool supportEmail = false) { return url.HandleTransformAccount( nameof(UsersController.ConfirmTransformToOrganization), accountToTransformUsername, confirmationToken, - relativeUrl); + relativeUrl, + supportEmail); } - public static string RejectTransformAccount(this UrlHelper url, string accountToTransformUsername, string confirmationToken, bool relativeUrl = true) + public static string RejectTransformAccount(this UrlHelper url, string accountToTransformUsername, string confirmationToken, bool relativeUrl = true, bool supportEmail = false) { return url.HandleTransformAccount( nameof(UsersController.RejectTransformToOrganization), accountToTransformUsername, confirmationToken, - relativeUrl); + relativeUrl, + supportEmail); } - private static string HandleTransformAccount(this UrlHelper url, string action, string accountToTransformUsername, string confirmationToken, bool relativeUrl = true) + private static string HandleTransformAccount(this UrlHelper url, string action, string accountToTransformUsername, string confirmationToken, bool relativeUrl = true, bool supportEmail = false) { return GetActionLink( url, @@ -1496,7 +1528,8 @@ private static string HandleTransformAccount(this UrlHelper url, string action, { { "accountNameToTransform", accountToTransformUsername }, { "token", confirmationToken } - }); + }, + supportEmail: supportEmail); } public static string CancelTransformAccount(this UrlHelper url, User accountToTransform, bool relativeUrl = true) @@ -1549,11 +1582,12 @@ public static string GetActionLink( bool relativeUrl, RouteValueDictionary routeValues = null, bool interceptReturnUrl = true, - string area = "" // Default to no area. Admin links should specify the "Admin" area explicitly. + string area = "", // Default to no area. Admin links should specify the "Admin" area explicitly. + bool supportEmail = false ) { var protocol = GetProtocol(url); - var hostName = GetConfiguredSiteHostName(); + var hostName = supportEmail ? GetConfiguredSupportEmailSiteHostName(): GetConfiguredSiteHostName(); routeValues = routeValues ?? new RouteValueDictionary(); if (!routeValues.ContainsKey(Area)) @@ -1600,10 +1634,12 @@ private static string GetRouteLink( UrlHelper url, string routeName, bool relativeUrl, - RouteValueDictionary routeValues = null) + RouteValueDictionary routeValues = null, + bool supportEmail = false) { var protocol = GetProtocol(url); - var hostName = GetConfiguredSiteHostName(); + + var hostName = supportEmail ? GetConfiguredSupportEmailSiteHostName() : GetConfiguredSiteHostName(); var routeLink = url.RouteUrl(routeName, routeValues, protocol, hostName); diff --git a/src/NuGetGallery/UrlHelperWrapper.cs b/src/NuGetGallery/UrlHelperWrapper.cs index 93048da74f..dd8c18c892 100644 --- a/src/NuGetGallery/UrlHelperWrapper.cs +++ b/src/NuGetGallery/UrlHelperWrapper.cs @@ -15,9 +15,9 @@ public UrlHelperWrapper(UrlHelper urlHelper) _urlHelper = urlHelper ?? throw new ArgumentNullException(nameof(urlHelper)); } - public string ConfirmPendingOwnershipRequest(string id, string username, string confirmationCode, bool relativeUrl) + public string ConfirmPendingOwnershipRequest(string id, string username, string confirmationCode, bool relativeUrl, bool supportEmail) { - return _urlHelper.ConfirmPendingOwnershipRequest(id, username, confirmationCode, relativeUrl); + return _urlHelper.ConfirmPendingOwnershipRequest(id, username, confirmationCode, relativeUrl, supportEmail); } public string ManagePackageOwnership(string id, bool relativeUrl) @@ -25,14 +25,14 @@ public string ManagePackageOwnership(string id, bool relativeUrl) return _urlHelper.ManagePackageOwnership(id, relativeUrl); } - public string Package(string id, string version, bool relativeUrl) + public string Package(string id, string version, bool relativeUrl, bool supportEmail) { - return _urlHelper.Package(id, version, relativeUrl); + return _urlHelper.Package(id, version, relativeUrl, supportEmail: supportEmail); } - public string RejectPendingOwnershipRequest(string id, string username, string confirmationCode, bool relativeUrl) + public string RejectPendingOwnershipRequest(string id, string username, string confirmationCode, bool relativeUrl, bool supportEmail) { - return _urlHelper.RejectPendingOwnershipRequest(id, username, confirmationCode, relativeUrl); + return _urlHelper.RejectPendingOwnershipRequest(id, username, confirmationCode, relativeUrl, supportEmail); } } } diff --git a/src/NuGetGallery/ViewModels/DisplayPackageViewModel.cs b/src/NuGetGallery/ViewModels/DisplayPackageViewModel.cs index 625fef4322..2540018b67 100644 --- a/src/NuGetGallery/ViewModels/DisplayPackageViewModel.cs +++ b/src/NuGetGallery/ViewModels/DisplayPackageViewModel.cs @@ -33,6 +33,7 @@ public class DisplayPackageViewModel : ListPackageItemViewModel public bool IsDotnetToolPackageType { get; set; } public bool IsDotnetNewTemplatePackageType { get; set; } + public bool IsMSBuildSdkPackageType { get; set; } public bool IsAtomFeedEnabled { get; set; } public bool IsPackageDeprecationEnabled { get; set; } public bool IsPackageVulnerabilitiesEnabled { get; set; } diff --git a/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml b/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml index 45fb3a46e6..52ad0e73c2 100644 --- a/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml +++ b/src/NuGetGallery/Views/Packages/DisplayPackage.cshtml @@ -78,6 +78,21 @@ } }; } + else if (Model.IsMSBuildSdkPackageType) + { + packageManagers = new PackageManagerViewModel[] + { + new PackageManagerViewModel("SDK") + { + Id = "sdk", + InstallPackageCommands = new [] { string.Format("", + Model.Id, Model.Version) }, + AlertLevel = AlertLevel.Info, + AlertMessage = string.Format("For projects that support Sdk, copy this XML node into the project file to reference the package."), + CopyLabel = "Copy the SDK XML node", + } + }; + } else { packageManagers = new PackageManagerViewModel[] diff --git a/src/NuGetGallery/Views/Statistics/_PivotTable.cshtml b/src/NuGetGallery/Views/Statistics/_PivotTable.cshtml index aa9af7c13f..f4c3353ae3 100644 --- a/src/NuGetGallery/Views/Statistics/_PivotTable.cshtml +++ b/src/NuGetGallery/Views/Statistics/_PivotTable.cshtml @@ -57,7 +57,7 @@ - Downloads + Downloads (last 6 weeks) diff --git a/src/NuGetGallery/Web.config b/src/NuGetGallery/Web.config index 56c5379009..02743e762a 100644 --- a/src/NuGetGallery/Web.config +++ b/src/NuGetGallery/Web.config @@ -85,6 +85,7 @@ + @@ -673,4 +674,4 @@ - + \ No newline at end of file diff --git a/tests/NuGetGallery.Facts/App_Start/ConfigurationServiceFacts.cs b/tests/NuGetGallery.Facts/App_Start/ConfigurationServiceFacts.cs index 57b9e61b9f..88d0e8f157 100644 --- a/tests/NuGetGallery.Facts/App_Start/ConfigurationServiceFacts.cs +++ b/tests/NuGetGallery.Facts/App_Start/ConfigurationServiceFacts.cs @@ -22,6 +22,7 @@ private class TestableConfigurationService : ConfigurationService public TestableConfigurationService() : base() { StubConfiguredSiteRoot = "http://aSiteRoot/"; + StubConfiguredSupportEmailSiteRoot = "http://aSupportEmailSiteRoot"; StubRequest = new Mock(); StubRequest.Setup(stub => stub.IsLocal).Returns(false); @@ -31,6 +32,7 @@ public TestableConfigurationService() : base() } public string StubConfiguredSiteRoot { get; set; } + public string StubConfiguredSupportEmailSiteRoot { get; set; } public Mock StubRequest { get; set; } protected override string GetAppSetting(string settingName) @@ -42,8 +44,14 @@ protected override string GetAppSetting(string settingName) return StubConfiguredSiteRoot; } + if (settingName == $"{SettingPrefix}{nameof(tempAppConfig.SupportEmailSiteRoot)}") + { + return StubConfiguredSupportEmailSiteRoot; + } + return string.Empty; } + } [Fact] @@ -100,6 +108,37 @@ public void WillThrowIfConfiguredSiteRootIsNotHttpOrHttps() Assert.Throws(() => configuration.GetSiteRoot(useHttps: false)); } + + [Fact] + public void WillGetTheConfiguredHttpsSupportEmailSiteRoot() + { + var configuration = new TestableConfigurationService(); + configuration.StubConfiguredSupportEmailSiteRoot = "https://aSupportEmailSiteRoot"; + + var siteRoot = configuration.GetSupportEmailSiteRoot(); + + Assert.Equal("https://aSupportEmailSiteRoot", siteRoot); + } + + [Fact] + public void WillThrowIfConfiguredSupportEmailSiteRootIsNotHttpOrHttps() + { + var configuration = new TestableConfigurationService(); + configuration.StubConfiguredSupportEmailSiteRoot = "ftp://theSupportEmailSiteRoot/"; + + Assert.Throws(() => configuration.GetSupportEmailSiteRoot()); + } + + [Fact] + public void WillUseHttpsWhenConfiguredSiteRootIsHttp() + { + var configuration = new TestableConfigurationService(); + configuration.StubConfiguredSupportEmailSiteRoot = "http://aSupportEmailSiteRoot"; + + var siteRoot = configuration.GetSupportEmailSiteRoot(); + + Assert.Equal("https://aSupportEmailSiteRoot", siteRoot); + } } public class TheReadSettingMethod diff --git a/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs index 9e8da1fefa..bfd58b850e 100644 --- a/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs @@ -804,7 +804,7 @@ public async Task WillCreateAndLogInTheUserWhenNotLinking() It.Is( msg => msg.User == authUser.User - && msg.ConfirmationUrl == TestUtility.GallerySiteRootHttps + "account/confirm/" + authUser.User.Username + "/" + authUser.User.EmailConfirmationToken), + && msg.ConfirmationUrl == TestUtility.GallerySupportEmailSiteRootHttps + "account/confirm/" + authUser.User.Username + "/" + authUser.User.EmailConfirmationToken), false, false)); @@ -892,7 +892,7 @@ public async Task WillNotAutoConfirmAndWillSendConfirmationEmailWhenNotExternalC UserInfo = new IdentityInformation("", "", authUser.User.UnconfirmedEmailAddress, "") }); - var confirmationUrl = TestUtility.GallerySiteRootHttps + "account/confirm/" + authUser.User.Username + "/" + authUser.User.EmailConfirmationToken; + var confirmationUrl = TestUtility.GallerySupportEmailSiteRootHttps + "account/confirm/" + authUser.User.Username + "/" + authUser.User.EmailConfirmationToken; var configurationService = GetConfigurationService(); var messageService = GetMock(); messageService @@ -960,7 +960,7 @@ public async Task WillNotAutoConfirmAndWillSendConfirmationEmailWhenModelRegiste UserInfo = new IdentityInformation("", "", "unconfirmed@example.com", "") }); - var confirmationUrl = TestUtility.GallerySiteRootHttps + "account/confirm/" + authUser.User.Username + "/" + authUser.User.EmailConfirmationToken; + var confirmationUrl = TestUtility.GallerySupportEmailSiteRootHttps + "account/confirm/" + authUser.User.Username + "/" + authUser.User.EmailConfirmationToken; var configurationService = GetConfigurationService(); var messageService = GetMock(); messageService @@ -1147,7 +1147,7 @@ public async Task GivenValidExternalAuth_ItCreatesAccountAndLinksCredential() It.Is( msg => msg.User == authUser.User - && msg.ConfirmationUrl == TestUtility.GallerySiteRootHttps + "account/confirm/" + authUser.User.Username + "/" + authUser.User.EmailConfirmationToken), + && msg.ConfirmationUrl == TestUtility.GallerySupportEmailSiteRootHttps + "account/confirm/" + authUser.User.Username + "/" + authUser.User.EmailConfirmationToken), false, false)); diff --git a/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs index e884659436..88b3ac0863 100644 --- a/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs @@ -1609,7 +1609,7 @@ public async Task GivenNoOldPassword_ItSendsAPasswordSetEmail() await controller.ChangePassword(new UserAccountViewModel()); // Assert - Assert.Equal(TestUtility.GallerySiteRootHttps + "account/setpassword/test/t0k3n", actualConfirmUrl); + Assert.Equal(TestUtility.GallerySupportEmailSiteRootHttps + "account/setpassword/test/t0k3n", actualConfirmUrl); GetMock().VerifyAll(); } diff --git a/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs b/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs index 873421f80c..69cbdea97d 100644 --- a/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs +++ b/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs @@ -131,6 +131,7 @@ private static IGalleryConfigurationService CreateTestConfigurationService() // We configure HTTP site root, but require SSL. var configurationService = new TestGalleryConfigurationService(); configurationService.Current.SiteRoot = TestUtility.GallerySiteRootHttp; + configurationService.Current.SupportEmailSiteRoot = TestUtility.GallerySupportEmailSiteRootHttps; configurationService.Current.RequireSSL = true; configurationService.Current.GalleryOwner = new MailAddress("support@example.com"); diff --git a/tests/NuGetGallery.Facts/TestUtils/TestUtility.cs b/tests/NuGetGallery.Facts/TestUtils/TestUtility.cs index c55609672b..ac027e798a 100644 --- a/tests/NuGetGallery.Facts/TestUtils/TestUtility.cs +++ b/tests/NuGetGallery.Facts/TestUtils/TestUtility.cs @@ -21,6 +21,7 @@ public static class TestUtility public static readonly string GallerySiteRootHttp = $"http://{galleryHostName}/"; public static readonly string GallerySiteRootHttps = $"https://{galleryHostName}/"; + public static readonly string GallerySupportEmailSiteRootHttps = $"https://{galleryHostName}/"; public static readonly string FakeUserName = "theUsername"; public static readonly int FakeUserKey = _key++;