Oof there's the Update of RepositoryController for ya

This commit is contained in:
Jordan Brown
2018-07-30 01:19:55 -04:00
parent 75373ed2e9
commit cdc891904e
12 changed files with 315 additions and 36 deletions
@@ -24,7 +24,13 @@ namespace Tgstation.Server.Api.Models
/// If the repository was cloned from GitHub.com. If <see langword="true"/> this enables test merge functionality
/// </summary>
[Permissions(DenyWrite = true)]
public bool IsGitHub { get; set; }
public bool? IsGitHub { get; set; }
/// <summary>
/// Do the equivalent of a git pull. Will attempt to merge unless <see cref="Reference"/> is also specified
/// </summary>
[Permissions(WriteRight = RepositoryRights.UpdateBranch)]
public bool? UpdateReference { get; set; }
/// <summary>
/// The branch or tag HEAD points to
@@ -5,10 +5,15 @@ namespace Tgstation.Server.Api.Models
/// <inheritdoc />
public sealed class RevisionInformation : Internal.RevisionInformation
{
/// <summary>
/// The <see cref="TestMerge"/> that was created with this <see cref="RevisionInformation"/>
/// </summary>
public TestMerge PrimaryTestMerge { get; set; }
/// <summary>
/// The <see cref="TestMerge"/>s active in the <see cref="RevisionInformation"/>
/// </summary>
public List<TestMerge> TestMerges { get; set; }
public List<TestMerge> ActiveTestMerges { get; set; }
/// <summary>
/// The <see cref="CompileJob"/>s made from the <see cref="RevisionInformation"/>
@@ -14,6 +14,21 @@ namespace Tgstation.Server.Host.Components.Repository
/// </summary>
bool IsGitHubRepository { get; }
/// <summary>
/// The <see cref="Octokit.Repository.Owner"/> if this <see cref="IsGitHubRepository"/>
/// </summary>
string GitHubOwner { get; }
/// <summary>
/// The <see cref="Octokit.Repository.Name"/> if this <see cref="IsGitHubRepository"/>
/// </summary>
string GitHubRepoName { get; }
/// <summary>
/// If <see cref="Reference"/> tracks an upstream branch
/// </summary>
bool Tracking { get; }
/// <summary>
/// The SHA of the <see cref="IRepository"/> HEAD
/// </summary>
@@ -46,9 +61,8 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="committerEmail">The e-mail of the merge committer</param>
/// <param name="accessString">The access string to fetch from the origin repository</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <param name="mergerIdentifier">A string to identify the user that merged</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the SHA of the new HEAD on success, <see langword="null"/> on merge conflict</returns>
Task<string> AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, string mergerIdentifier, CancellationToken cancellationToken);
Task<string> AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, CancellationToken cancellationToken);
/// <summary>
/// Fetch commits from the origin repository
@@ -22,6 +22,15 @@ namespace Tgstation.Server.Host.Components.Repository
/// <inheritdoc />
public bool IsGitHubRepository { get; }
/// <inheritdoc />
public string GitHubOwner { get; }
/// <inheritdoc />
public string GitHubRepoName { get; }
/// <inheritdoc />
public bool Tracking => repository.Head.IsTracking;
/// <inheritdoc />
public string Head => repository.Head.Tip.Sha;
@@ -51,6 +60,19 @@ namespace Tgstation.Server.Host.Components.Repository
/// </summary>
readonly Action onDispose;
static void GetRepositoryOwnerName(string remote, out string owner, out string name)
{
//Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1]
//Yes use .git twice in case it was weird
var toRemove = new string[] { ".git", "/", ".git" };
foreach (string item in toRemove)
if (remote.EndsWith(item, StringComparison.OrdinalIgnoreCase))
remote = remote.Substring(0, remote.LastIndexOf(item, StringComparison.OrdinalIgnoreCase));
var splits = remote.Split('/');
name = splits[splits.Length - 1];
owner = splits[splits.Length - 2].Split('.')[0];
}
/// <summary>
/// Construct a <see cref="Repository"/>
/// </summary>
@@ -65,6 +87,12 @@ namespace Tgstation.Server.Host.Components.Repository
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
IsGitHubRepository = Origin.ToUpperInvariant().Contains("://GITHUB.COM/");
if (IsGitHubRepository)
{
GetRepositoryOwnerName(Origin, out var owner, out var name);
GitHubOwner = owner;
GitHubRepoName = name;
}
}
/// <inheritdoc />
@@ -106,7 +134,7 @@ namespace Tgstation.Server.Host.Components.Repository
}
/// <inheritdoc />
public async Task<string> AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, string mergerIdentifier, CancellationToken cancellationToken)
public async Task<string> AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, CancellationToken cancellationToken)
{
if (!IsGitHubRepository)
@@ -116,14 +116,14 @@ namespace Tgstation.Server.Host.Components.Watchdog
InstanceName = instance.Name,
Revision = dmbProvider.CompileJob.RevisionInformation
};
interopInfo.TestMerges.AddRange(dmbProvider.CompileJob.RevisionInformation.TestMerges.Select(x => new TestMerge
interopInfo.TestMerges.AddRange(dmbProvider.CompileJob.RevisionInformation.ActiveTestMerges.Select(x => x.TestMerge).Select(x => new TestMerge
{
Author = x.Author,
Body = x.BodyAtMerge,
Comment = x.Comment,
CommitSha = x.RevisionInformation.CommitSha,
CommitSha = x.PrimaryRevisionInformation.CommitSha,
Number = x.Number,
OriginCommitSha = x.RevisionInformation.OriginCommitSha,
OriginCommitSha = x.PrimaryRevisionInformation.OriginCommitSha,
PullRequestCommit = x.PullRequestRevision,
TimeMerged = x.MergedAt.Ticks,
Title = x.TitleAtMerge,
@@ -3,7 +3,9 @@ using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
@@ -15,7 +17,7 @@ using Tgstation.Server.Host.Security;
namespace Tgstation.Server.Host.Controllers
{
/// <summary>
/// Controller for managing the <see cref="Api.Models.Repository"/>s
/// Controller for managing the <see cref="Repository"/>s
/// </summary>
[Route("/" + nameof(Repository))]
public sealed class RepositoryController : ModelController<Repository>
@@ -25,30 +27,32 @@ namespace Tgstation.Server.Host.Controllers
/// </summary>
readonly IInstanceManager instanceManager;
/// <summary>
/// The <see cref="Octokit.IGitHubClient"/> for the <see cref="RepositoryController"/>
/// </summary>
readonly Octokit.IGitHubClient gitHubClient;
/// <summary>
/// Construct a <see cref="RepositoryController"/>
/// </summary>
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="ApiController"/></param>
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
/// <param name="instanceManager">The value of <see cref="instanceManager"/></param>
public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory, true)
/// <param name="gitHubClient">The value of <see cref="gitHubClient"/></param>
public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, Octokit.IGitHubClient gitHubClient) : base(databaseContext, authenticationContextFactory, true)
{
this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient));
}
static string GetAccessString(Api.Models.Internal.RepositorySettings repositorySettings) => repositorySettings.AccessUser != null ? String.Concat(repositorySettings.AccessUser, '@', repositorySettings.AccessToken) : null;
async Task<bool> PopulateApi(Repository model, Components.Repository.IRepository repository, string lastOriginCommitSha, CancellationToken cancellationToken)
async Task<bool> LoadRevisionInformation(Components.Repository.IRepository repository, Action<Models.RevisionInformation> revInfoSink, CancellationToken cancellationToken)
{
model.IsGitHub = repository.IsGitHubRepository;
model.Origin = repository.Origin;
model.Reference = repository.Reference;
model.Sha = repository.Head;
//rev info stuff
var revisionInfo = await DatabaseContext.RevisionInformations.Where(x => x.CommitSha == model.Sha)
var repoSha = repository.Head;
var revisionInfo = await DatabaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha)
.Include(x => x.CompileJobs)
.Include(x => x.TestMerges) //minimal info, they can query the rest if they're allowed
.Include(x => x.ActiveTestMerges) //minimal info, they can query the rest if they're allowed
.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); //search every rev info because LOL SHA COLLISIONS
var needsDbUpdate = revisionInfo == default;
@@ -57,16 +61,30 @@ namespace Tgstation.Server.Host.Controllers
//needs insertion
revisionInfo = new Models.RevisionInformation
{
CommitSha = model.Sha,
CommitSha = repoSha,
CompileJobs = new List<Models.CompileJob>(),
TestMerges = new List<Models.TestMerge>(), //non null vals for api returns
OriginCommitSha = lastOriginCommitSha ?? model.Sha
ActiveTestMerges = new List<Models.RevInfoTestMerge>() //non null vals for api returns
};
DatabaseContext.RevisionInformations.Add(revisionInfo);
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
lock (DatabaseContext) //cleaner this way
DatabaseContext.RevisionInformations.Add(revisionInfo);
}
revInfoSink?.Invoke(revisionInfo);
return needsDbUpdate;
}
async Task<bool> PopulateApi(Repository model, Components.Repository.IRepository repository, string lastOriginCommitSha, Action<Models.RevisionInformation> revInfoSink, CancellationToken cancellationToken)
{
model.IsGitHub = repository.IsGitHubRepository;
model.Origin = repository.Origin;
model.Reference = repository.Reference;
model.Sha = repository.Head;
//rev info stuff
Models.RevisionInformation revisionInfo = null;
var needsDbUpdate = await LoadRevisionInformation(repository, x => revisionInfo = x, cancellationToken).ConfigureAwait(false);
revisionInfo.OriginCommitSha = lastOriginCommitSha ?? model.Sha;
revInfoSink?.Invoke(revisionInfo);
model.RevisionInformation = revisionInfo.ToApi();
return needsDbUpdate;
}
@@ -102,7 +120,7 @@ namespace Tgstation.Server.Host.Controllers
//clone conflict
return Conflict();
var api = currentModel.ToApi();
await PopulateApi(api, repo, null, cancellationToken).ConfigureAwait(false);
await PopulateApi(api, repo, null, null, cancellationToken).ConfigureAwait(false);
currentModel.LastOriginCommitSha = repo.Head;
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
return Json(api);
@@ -146,7 +164,164 @@ namespace Tgstation.Server.Host.Controllers
using (var repo = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false))
{
if (await PopulateApi(api, repo, currentModel.LastOriginCommitSha, cancellationToken).ConfigureAwait(false))
if (await PopulateApi(api, repo, currentModel.LastOriginCommitSha, null, cancellationToken).ConfigureAwait(false))
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
return Json(api);
}
}
/// <inheritdoc />
[TgsAuthorize(RepositoryRights.ChangeAutoUpdateSettings | RepositoryRights.ChangeCommitter | RepositoryRights.ChangeCredentials | RepositoryRights.ChangeTestMergeCommits | RepositoryRights.MergePullRequest | RepositoryRights.SetReference | RepositoryRights.SetSha | RepositoryRights.UpdateBranch)]
public override async Task<IActionResult> Update([FromBody]Repository model, CancellationToken cancellationToken)
{
if (model == null)
return BadRequest(new { message = "Missing request model!" });
if (model.AccessUser == null ^ model.AccessToken == null)
return BadRequest(new { message = "Either both accessToken and accessUser must be present or neither!" });
if (model.Sha != null && model.Reference != null)
return BadRequest(new { message = "Only one of sha or reference may be specified!" });
if(model.Sha != null && model.UpdateReference == true)
return BadRequest(new { message = "Cannot update a reference when checking out a sha!" });
if (model.Origin != null)
return BadRequest(new { message = "origin cannot be modified without deleting the repository!" });
var newTestMerges = model.NewTestMerges != null && model.NewTestMerges.Count > 0;
var userRights = (RepositoryRights)AuthenticationContext.GetRight(RightsType.Repository);
if (newTestMerges && !userRights.HasFlag(RepositoryRights.MergePullRequest))
return Forbid();
var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (currentModel == default)
return StatusCode((int)HttpStatusCode.Gone);
bool CheckModified<T>(Expression<Func<Api.Models.Internal.RepositorySettings, T>> expression, RepositoryRights requiredRight)
{
var memberSelectorExpression = (MemberExpression)expression.Body;
var property = (PropertyInfo)memberSelectorExpression.Member;
var newVal = property.GetValue(model);
if (newVal == null)
return false;
if (!userRights.HasFlag(requiredRight) && property.GetValue(currentModel) != newVal)
return true;
property.SetValue(currentModel, newVal);
return false;
};
if (CheckModified(x => x.AccessToken, RepositoryRights.ChangeCredentials)
|| CheckModified(x => x.AccessUser, RepositoryRights.ChangeCredentials)
|| CheckModified(x => x.AutoUpdatesKeepTestMerges, RepositoryRights.ChangeAutoUpdateSettings)
|| CheckModified(x => x.AutoUpdatesSynchronize, RepositoryRights.ChangeAutoUpdateSettings)
|| CheckModified(x => x.CommitterEmail, RepositoryRights.ChangeCommitter)
|| CheckModified(x => x.CommitterName, RepositoryRights.ChangeCommitter)
|| CheckModified(x => x.PushTestMergeCommits, RepositoryRights.ChangeTestMergeCommits)
|| CheckModified(x => x.ShowTestMergeCommitters, RepositoryRights.ChangeTestMergeCommits))
return Forbid();
//so that's the stuff that is just db, the rest is tricky
using (var repo = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false))
{
if (newTestMerges && !repo.IsGitHubRepository)
return Conflict(new { message = "Cannot test merge on a non GitHub based repository!" });
var committerName = currentModel.ShowTestMergeCommitters.Value ? AuthenticationContext.User.Name : currentModel.CommitterName;
var accessString = GetAccessString(currentModel);
if (model.UpdateReference == true)
{
if (!repo.Tracking && model.Reference == null)
return Conflict(new { message = "Not on an updatable reference!" });
if (!userRights.HasFlag(RepositoryRights.UpdateBranch))
return Forbid();
await repo.FetchOrigin(accessString, cancellationToken).ConfigureAwait(false);
if (model.Sha == null && model.Reference == null)
await repo.MergeOrigin(committerName, currentModel.CommitterEmail, cancellationToken).ConfigureAwait(false);
}
if (model.Sha != null || model.Reference != null)
{
await repo.CheckoutObject(model.Sha ?? model.Reference, cancellationToken).ConfigureAwait(false);
if (model.UpdateReference == true && model.Reference != null)
{
if (!repo.Tracking)
return Conflict(new { message = "Checked out reference is does not track an object!" });
await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false);
}
}
//now add any testmerges
if (newTestMerges)
{
var repoOwner = repo.GitHubOwner;
var repoName = repo.GitHubRepoName;
var allAddedTestMerges = new List<Models.TestMerge>();
foreach (var I in model.NewTestMerges)
{
await repo.AddTestMerge(I.Number, I.PullRequestRevision, committerName, currentModel.CommitterEmail, accessString, cancellationToken).ConfigureAwait(false);
Octokit.PullRequest pr = null;
string errorMessage = null;
Models.RevisionInformation revisionInformation = null;
var revInfoTask = LoadRevisionInformation(repo, x => revisionInformation = x, cancellationToken);
try
{
pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number).ConfigureAwait(false);
}
catch (Octokit.RateLimitExceededException)
{
//you look at your anonymous access and sigh
errorMessage = "PRE RATE LIMITED";
}
catch (Octokit.NotFoundException)
{
//you look at your shithub access and sigh
errorMessage = "PRE NOT FOUND";
}
var attachedContextUser = new Models.User
{
Id = AuthenticationContext.User.Id
};
DatabaseContext.Users.Attach(attachedContextUser);
var tm = new Models.TestMerge
{
Author = pr?.User.Login ?? errorMessage,
BodyAtMerge = pr?.Body,
MergedAt = DateTimeOffset.Now,
TitleAtMerge = pr?.Title ?? errorMessage,
Comment = I.Comment,
Number = I.Number,
MergedBy = attachedContextUser,
PullRequestRevision = I.PullRequestRevision,
Url = pr?.HtmlUrl ?? errorMessage
};
if (pr == null)
tm.BodyAtMerge = errorMessage;
allAddedTestMerges.Add(tm);
await revInfoTask.ConfigureAwait(false);
revisionInformation.PrimaryTestMerge = tm;
revisionInformation.ActiveTestMerges.AddRange(allAddedTestMerges.Select(x => new RevInfoTestMerge
{
TestMerge = x
}));
}
}
var api = currentModel.ToApi();
if (await PopulateApi(api, repo, currentModel.LastOriginCommitSha, null, cancellationToken).ConfigureAwait(false) || newTestMerges)
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
return Json(api);
}
@@ -56,6 +56,11 @@ namespace Tgstation.Server.Host.Models
/// <inheritdoc />
public DbSet<ReattachInformation> ReattachInformations { get; set; }
/// <summary>
/// The <see cref="RevInfoTestMerge"/>s om the <see cref="DatabaseContext{TParentContext}"/>
/// </summary>
public DbSet<RevInfoTestMerge> RevInfoTestMerges { get; set; }
/// <inheritdoc />
public DbSet<WatchdogReattachInformation> WatchdogReattachInformations { get; set; }
@@ -96,9 +101,12 @@ namespace Tgstation.Server.Host.Models
modelBuilder.Entity<InstanceUser>().HasIndex(x => new { x.UserId, x.InstanceId }).IsUnique();
modelBuilder.Entity<TestMerge>().HasMany(x => x.RevisonInformations).WithOne(x => x.TestMerge).OnDelete(DeleteBehavior.Cascade);
var revInfo = modelBuilder.Entity<RevisionInformation>();
revInfo.HasMany(x => x.CompileJobs).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.Cascade);
revInfo.HasMany(x => x.TestMerges).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.Cascade);
revInfo.HasMany(x => x.ActiveTestMerges).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.Cascade);
revInfo.HasOne(x => x.PrimaryTestMerge).WithOne(x => x.PrimaryRevisionInformation).OnDelete(DeleteBehavior.SetNull);
revInfo.HasIndex(x => x.CommitSha).IsUnique();
var chatChannel = modelBuilder.Entity<ChatChannel>();
@@ -24,7 +24,7 @@ namespace Tgstation.Server.Host.Models
/// <inheritdoc />
public Repository ToApi() => new Repository {
AccessToken = AccessToken,
//AccessToken = AccessToken, //never show this
AccessUser = AccessUser,
AutoUpdatesKeepTestMerges = AutoUpdatesKeepTestMerges,
AutoUpdatesSynchronize = AutoUpdatesSynchronize,
@@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;
namespace Tgstation.Server.Host.Models
{
/// <summary>
/// Many to many relationship for <see cref="Models.RevisionInformation"/> and <see cref="Models.TestMerge"/>
/// </summary>
public sealed class RevInfoTestMerge
{
/// <summary>
/// The row Id
/// </summary>
public long Id { get; set; }
/// <summary>
/// The <see cref="Models.TestMerge"/>
/// </summary>
[Required]
public TestMerge TestMerge { get; set; }
/// <summary>
/// The <see cref="Models.RevisionInformation"/>
/// </summary>
[Required]
public RevisionInformation RevisionInformation { get; set; }
}
}
@@ -17,9 +17,14 @@ namespace Tgstation.Server.Host.Models
public Instance Instance { get; set; }
/// <summary>
/// See <see cref="Api.Models.RevisionInformation.TestMerges"/>
/// See <see cref="Api.Models.RevisionInformation.PrimaryTestMerge"/>
/// </summary>
public List<TestMerge> TestMerges { get; set; }
public TestMerge PrimaryTestMerge { get; set; }
/// <summary>
/// See <see cref="Api.Models.RevisionInformation.ActiveTestMerges"/>
/// </summary>
public List<RevInfoTestMerge> ActiveTestMerges { get; set; }
/// <summary>
/// See <see cref="CompileJob"/>s made from this <see cref="RevisionInformation"/>
@@ -31,7 +36,8 @@ namespace Tgstation.Server.Host.Models
{
CommitSha = CommitSha,
OriginCommitSha = OriginCommitSha,
TestMerges = TestMerges.Select(x => x.ToApi()).ToList(),
PrimaryTestMerge = PrimaryTestMerge?.ToApi(),
ActiveTestMerges = ActiveTestMerges.Select(x => x.TestMerge.ToApi()).ToList(),
CompileJobs = CompileJobs.Select(x => x.ToApi()).ToList()
};
}
+14 -4
View File
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Tgstation.Server.Host.Models
{
@@ -12,10 +13,19 @@ namespace Tgstation.Server.Host.Models
public User MergedBy { get; set; }
/// <summary>
/// The <see cref="Models.RevisionInformation"/> for the <see cref="TestMerge"/>
/// The initial <see cref="RevisionInformation"/> the <see cref="TestMerge"/> was merged with
/// </summary>
[Required]
public RevisionInformation RevisionInformation { get; set; }
public RevisionInformation PrimaryRevisionInformation { get; set; }
/// <summary>
/// Foreign key for <see cref="PrimaryRevisionInformation"/>
/// </summary>
public long? PrimaryRevisionInformationId { get; set; }
/// <summary>
/// All the <see cref="RevInfoTestMerge"/> for the <see cref="TestMerge"/>
/// </summary>
public List<RevInfoTestMerge> RevisonInformations { get; set; }
/// <inheritdoc />
public Api.Models.TestMerge ToApi() => new Api.Models.TestMerge
@@ -64,7 +64,7 @@ namespace Tgstation.Server.Host.Security
var res = NativeMethods.LogonUser(splits.Length > 1 ? splits[1] : splits[0], splits.Length > 1 ? splits[0] : null, password, 3 /*LOGON32_LOGON_NETWORK*/, 0 /*LOGON32_PROVIDER_DEFAULT*/, out var token);
if (!res)
throw new Win32Exception(Marshal.GetLastWin32Error());
return null;
using (var handle = new SafeAccessTokenHandle(token)) //checked internally, windows identity always duplicates the handle when constructed with a userToken
return (ISystemIdentity)new WindowsSystemIdentity(new WindowsIdentity(handle.DangerousGetHandle())); //https://github.com/dotnet/corefx/blob/6ed61acebe3214fcf79b4274f2bb9b55c0604a4d/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs#L271