Skip to content

Commit

Permalink
Merge pull request #5650 from NuGet/dev
Browse files Browse the repository at this point in the history
Merge Gallery dev to master
  • Loading branch information
cristinamanum authored Mar 21, 2018
2 parents a4dcd89 + a3aed57 commit 4607a1d
Show file tree
Hide file tree
Showing 69 changed files with 1,650 additions and 138 deletions.
4 changes: 4 additions & 0 deletions content/DEV/Team.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"anangaur",
"anurse",
"bhuvak",
"bsimser",
"chenriksson",
"claycompton",
"cristinamanum",
Expand All @@ -23,10 +24,12 @@
"diverdan92",
"dotnetjunky",
"drewgillies",
"drusellers",
"dtivel",
"emgarten",
"ericstj",
"feiling",
"ferventcoder",
"half-ogre",
"harikmenon",
"harshgMSFT",
Expand All @@ -47,6 +50,7 @@
"murilogr",
"nkolev92",
"osbornm",
"PatoBeltran",
"pranavkm",
"pspill",
"rohit21agrawal",
Expand Down
4 changes: 4 additions & 0 deletions content/INT/Team.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"anangaur",
"anurse",
"bhuvak",
"bsimser",
"chenriksson",
"claycompton",
"cristinamanum",
Expand All @@ -23,10 +24,12 @@
"diverdan92",
"dotnetjunky",
"drewgillies",
"drusellers",
"dtivel",
"emgarten",
"ericstj",
"feiling",
"ferventcoder",
"half-ogre",
"harikmenon",
"harshgMSFT",
Expand All @@ -47,6 +50,7 @@
"murilogr",
"nkolev92",
"osbornm",
"PatoBeltran",
"pranavkm",
"pspill",
"rohit21agrawal",
Expand Down
4 changes: 4 additions & 0 deletions content/PROD/Team.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"anangaur",
"anurse",
"bhuvak",
"bsimser",
"chenriksson",
"claycompton",
"cristinamanum",
Expand All @@ -23,10 +24,12 @@
"diverdan92",
"dotnetjunky",
"drewgillies",
"drusellers",
"dtivel",
"emgarten",
"ericstj",
"feiling",
"ferventcoder",
"half-ogre",
"harikmenon",
"harshgMSFT",
Expand All @@ -47,6 +50,7 @@
"murilogr",
"nkolev92",
"osbornm",
"PatoBeltran",
"pranavkm",
"pspill",
"rohit21agrawal",
Expand Down
4 changes: 4 additions & 0 deletions src/Bootstrap/dist/css/bootstrap-theme.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/Bootstrap/less/theme/all.less
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@
@import "page-statistics-per-package.less";
@import "page-status.less";
@import "page-transform-account.less";
@import "page-upload.less";
@import "page-upload.less";
@import "page-header.less";
4 changes: 4 additions & 0 deletions src/Bootstrap/less/theme/page-header.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.warning-icon {
vertical-align: bottom;
color: #ea7918;
}
9 changes: 7 additions & 2 deletions src/NuGetGallery.Core/Authentication/NuGetClaims.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,13 @@ public static class NuGetClaims
public const string DiscontinuedLogin = ClaimsDomain + "discontinuedlogin";

/// <summary>
/// The value of <see cref="DiscontinuedLogin"/> when the user is authenticated with a discontinued <see cref="Credential"/>.
/// The claim url for the claim that stores whether or not the user has a password login.
/// </summary>
public const string DiscontinuedLoginValue = "true";
public const string PasswordLogin = ClaimsDomain + "passwordlogin";

/// <summary>
/// The claim url for the claim that stores whether or not the user has an external login.
/// </summary>
public const string ExternalLogin = ClaimsDomain + "externallogin";
}
}
8 changes: 2 additions & 6 deletions src/NuGetGallery.Core/Entities/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ public User(string username)

public bool IsDeleted { get; set; }

public bool EnableMultiFactorAuthentication { get; set; }

public virtual ICollection<ReservedNamespace> ReservedNamespaces { get; set; }

[DefaultValue(true)]
Expand Down Expand Up @@ -161,12 +163,6 @@ public void UpdateEmailAddress(string newEmailAddress, Func<string> generateToke
EmailConfirmationToken = generateToken();
}

public bool HasPassword()
{
return Credentials.Any(c =>
c.Type.StartsWith(CredentialTypes.Password.Prefix, StringComparison.OrdinalIgnoreCase));
}

public bool IsInRole(string roleName)
{
return Roles.Any(r => String.Equals(r.Name, roleName, StringComparison.OrdinalIgnoreCase));
Expand Down
2 changes: 2 additions & 0 deletions src/NuGetGallery.Core/NuGetGallery.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,10 @@
<Compile Include="CoreConstants.cs" />
<Compile Include="CredentialTypes.cs" />
<Compile Include="Completion.cs" />
<Compile Include="Diagnostics\DiagnosticsServiceExtensions.cs" />
<Compile Include="Diagnostics\IDiagnosticsService.cs" />
<Compile Include="Diagnostics\IDiagnosticsSource.cs" />
<Compile Include="Diagnostics\NullDiagnosticsSource.cs" />
<Compile Include="DisposableAction.cs" />
<Compile Include="Entities\Certificate.cs" />
<Compile Include="Entities\Credential.cs" />
Expand Down
9 changes: 8 additions & 1 deletion src/NuGetGallery.Core/Services/AccessConditionWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,20 @@ public class AccessConditionWrapper : IAccessCondition
private AccessConditionWrapper(string ifNoneMatchETag, string ifMatchETag)
{
IfNoneMatchETag = ifNoneMatchETag;
IfMatchETag = IfMatchETag;
IfMatchETag = ifMatchETag;
}

public string IfNoneMatchETag { get; }

public string IfMatchETag { get; }

public static IAccessCondition GenerateEmptyCondition()
{
return new AccessConditionWrapper(
ifNoneMatchETag: null,
ifMatchETag: null);
}

public static IAccessCondition GenerateIfMatchCondition(string etag)
{
return new AccessConditionWrapper(
Expand Down
24 changes: 24 additions & 0 deletions src/NuGetGallery.Core/Services/CloudBlobClientWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.RetryPolicies;

Expand All @@ -18,6 +21,27 @@ public CloudBlobClientWrapper(string storageConnectionString, bool readAccessGeo
_readAccessGeoRedundant = readAccessGeoRedundant;
}

public ISimpleCloudBlob GetBlobFromUri(Uri uri)
{
// For Azure blobs, the query string is assumed to be the SAS token.
ISimpleCloudBlob blob;
if (!string.IsNullOrEmpty(uri.Query))
{
var uriBuilder = new UriBuilder(uri);
uriBuilder.Query = string.Empty;

blob = new CloudBlobWrapper(new CloudBlockBlob(
uriBuilder.Uri,
new StorageCredentials(uri.Query)));
}
else
{
blob = new CloudBlobWrapper(new CloudBlockBlob(uri));
}

return blob;
}

public ICloudBlobContainer GetContainerReference(string containerAddress)
{
if (_blobClient == null)
Expand Down
72 changes: 67 additions & 5 deletions src/NuGetGallery.Core/Services/CloudBlobCoreFileStorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.Blob.Protocol;
using NuGetGallery.Diagnostics;

namespace NuGetGallery
{
Expand Down Expand Up @@ -40,11 +41,13 @@ public class CloudBlobCoreFileStorageService : ICoreFileStorageService
};

protected readonly ICloudBlobClient _client;
protected readonly IDiagnosticsSource _trace;
protected readonly ConcurrentDictionary<string, ICloudBlobContainer> _containers = new ConcurrentDictionary<string, ICloudBlobContainer>();

public CloudBlobCoreFileStorageService(ICloudBlobClient client)
public CloudBlobCoreFileStorageService(ICloudBlobClient client, IDiagnosticsService diagnosticsService)
{
_client = client;
_client = client ?? throw new ArgumentNullException(nameof(client));
_trace = diagnosticsService?.SafeGetSource(nameof(CloudBlobCoreFileStorageService)) ?? throw new ArgumentNullException(nameof(diagnosticsService));
}

public async Task DeleteFileAsync(string folderName, string fileName)
Expand Down Expand Up @@ -110,6 +113,22 @@ public async Task<IFileReference> GetFileReferenceAsync(string folderName, strin
}
}

public Task CopyFileAsync(
Uri srcUri,
string destFolderName,
string destFileName,
IAccessCondition destAccessCondition)
{
if (srcUri == null)
{
throw new ArgumentNullException(nameof(srcUri));
}

var srcBlob = _client.GetBlobFromUri(srcUri);

return CopyFileAsync(srcBlob, destFolderName, destFileName, destAccessCondition);
}

public async Task<string> CopyFileAsync(
string srcFolderName,
string srcFileName,
Expand All @@ -127,6 +146,18 @@ public async Task<string> CopyFileAsync(
throw new ArgumentNullException(nameof(srcFileName));
}

var srcContainer = await GetContainerAsync(srcFolderName);
var srcBlob = srcContainer.GetBlobReference(srcFileName);

return await CopyFileAsync(srcBlob, destFolderName, destFileName, destAccessCondition);
}

private async Task<string> CopyFileAsync(
ISimpleCloudBlob srcBlob,
string destFolderName,
string destFileName,
IAccessCondition destAccessCondition)
{
if (destFolderName == null)
{
throw new ArgumentNullException(nameof(destFolderName));
Expand All @@ -137,9 +168,6 @@ public async Task<string> CopyFileAsync(
throw new ArgumentNullException(nameof(destFileName));
}

var srcContainer = await GetContainerAsync(srcFolderName);
var srcBlob = srcContainer.GetBlobReference(srcFileName);

var destContainer = await GetContainerAsync(destFolderName);
var destBlob = destContainer.GetBlobReference(destFileName);
destAccessCondition = destAccessCondition ?? AccessConditionWrapper.GenerateIfNotExistsCondition();
Expand All @@ -162,17 +190,37 @@ public async Task<string> CopyFileAsync(
// condition is. This is because the source blob is preferable over a failed copy. We use the etag
// of the failed blob to avoid inadvertently replacing a blob that is now valid (i.e. has a
// successful copy status).
_trace.TraceEvent(
TraceEventType.Information,
id: 0,
message: $"Destination blob '{destFolderName}/{destFileName}' already exists but has a " +
$"failed copy status. This blob will be replaced if the etag matches '{destBlob.ETag}'.");

mappedDestAccessCondition = AccessCondition.GenerateIfMatchCondition(destBlob.ETag);
}
else if ((srcBlob.Properties.ContentMD5 != null
&& srcBlob.Properties.ContentMD5 == destBlob.Properties.ContentMD5
&& srcBlob.Properties.Length == destBlob.Properties.Length))
{
// If the blob hash is the same and the length is the same, no-op the copy.
_trace.TraceEvent(
TraceEventType.Information,
id: 0,
message: $"Destination blob '{destFolderName}/{destFileName}' already has hash " +
$"'{destBlob.Properties.ContentMD5}' and length '{destBlob.Properties.Length}'. The copy " +
$"will be skipped.");

return srcBlob.ETag;
}
}

_trace.TraceEvent(
TraceEventType.Information,
id: 0,
message: $"Copying of source blob '{srcBlob.Uri}' to '{destFolderName}/{destFileName}' with source " +
$"access condition {Log(srcAccessCondition)} and destination access condition " +
$"{Log(mappedDestAccessCondition)}.");

// Start the server-side copy and wait for it to complete. If "If-None-Match: *" was specified and the
// destination already exists, HTTP 409 is thrown. If "If-Match: ETAG" was specified and the destination
// has changed, HTTP 412 is thrown.
Expand Down Expand Up @@ -214,6 +262,20 @@ await destBlob.StartCopyAsync(
return srcBlob.ETag;
}

private static string Log(AccessCondition accessCondition)
{
if (accessCondition?.IfMatchETag != null)
{
return $"'If-Match: {accessCondition.IfMatchETag}'";
}
else if (accessCondition?.IfNoneMatchETag != null)
{
return $"'If-None-Match: {accessCondition.IfNoneMatchETag}'";
}

return "(none)";
}

public async Task SaveFileAsync(string folderName, string fileName, Stream packageFile, bool overwrite = true)
{
ICloudBlobContainer container = await GetContainerAsync(folderName);
Expand Down
9 changes: 8 additions & 1 deletion src/NuGetGallery.Core/Services/CorePackageFileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,17 @@ public async Task StorePackageFileInBackupLocationAsync(Package package, Stream
version = package.NormalizedVersion;
}

// Hash the provided stream instead of using the hash on the package. This is to avoid a backup with the
// incorrect file name if the hash in the DB does not match the package (a potentially transient issue).
var hash = CryptographyService.GenerateHash(
packageFile,
hashAlgorithmId: CoreConstants.Sha512HashAlgorithmId);
packageFile.Position = 0;

var fileName = BuildBackupFileName(
package.PackageRegistration.Id,
version,
package.Hash);
hash);

// If the package already exists, don't even bother uploading it. The file name is based off of the hash so
// we know the upload isn't necessary.
Expand Down
2 changes: 1 addition & 1 deletion src/NuGetGallery.Core/Services/CryptographyService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public static class CryptographyService
{
public static string GenerateHash(
Stream input,
string hashAlgorithmId = CoreConstants.Sha512HashAlgorithmId)
string hashAlgorithmId)
{
input.Position = 0;

Expand Down
4 changes: 4 additions & 0 deletions src/NuGetGallery.Core/Services/ICloudBlobClient.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;

namespace NuGetGallery
{
public interface ICloudBlobClient
{
ICloudBlobContainer GetContainerReference(string containerAddress);

ISimpleCloudBlob GetBlobFromUri(Uri uri);
}
}
Loading

0 comments on commit 4607a1d

Please sign in to comment.