Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GitHub Enterprise Server support #286

Merged
merged 29 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f3b045a
Explicitly disallow multiple accounts on GitHub extension
vineeththomasalex Nov 14, 2023
b3b92ff
Merge branch 'main' of https://github.com/microsoft/devhomegithubexte…
vineeththomasalex Nov 27, 2023
fc2e7c8
Fix constructor bug
vineeththomasalex Nov 12, 2023
8f59238
Tested GHES
vineeththomasalex Nov 13, 2023
2c4ed2e
Stashing
vineeththomasalex Nov 14, 2023
2355ed8
Basic flow works
vineeththomasalex Nov 16, 2023
cb53cc6
Validation added
vineeththomasalex Nov 17, 2023
1e48c33
Minor updates
vineeththomasalex Nov 17, 2023
452f39d
Minor Widget update
vineeththomasalex Nov 17, 2023
84ca2ff
SearchManager minor update
vineeththomasalex Nov 17, 2023
542305a
Fixed LoginUI
vineeththomasalex Nov 23, 2023
f9bdd76
Created separate states for pages
vineeththomasalex Nov 24, 2023
febc384
Added CredentialVault Tests
vineeththomasalex Nov 25, 2023
b2639d6
Added LoginUI tests
vineeththomasalex Nov 26, 2023
04360df
Added some tests
vineeththomasalex Nov 26, 2023
d4dd718
Reverted Widget updates
vineeththomasalex Nov 26, 2023
ccda493
Fixed tests
vineeththomasalex Nov 27, 2023
c5f40ee
Revert changes to allow multi-user for tests
vineeththomasalex Nov 27, 2023
4f1159c
Update GitHubExt-CI.yml
vineeththomasalex Nov 28, 2023
28ae79c
Update GitHubExt-CI.yml
vineeththomasalex Nov 28, 2023
9c5189e
PR Comments 1
vineeththomasalex Dec 11, 2023
1cfa81a
PR comments 2
vineeththomasalex Dec 11, 2023
7ed7eb3
Merge branch 'user/vineeththomasalex/GHES_3' of https://github.com/mi…
vineeththomasalex Dec 27, 2023
0799379
Ignore some tests in pipeline
vineeththomasalex Dec 27, 2023
54fcfed
PR Comments 3
vineeththomasalex Dec 29, 2023
cc1faf5
PR Comments 4
vineeththomasalex Jan 5, 2024
dec0a5c
Minor update
vineeththomasalex Jan 5, 2024
1374d11
PR comments 6
vineeththomasalex Jan 10, 2024
dda9219
PR Comments 7
vineeththomasalex Jan 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/GitHubExtension/Client/GithubClientProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,16 @@ public async Task<GitHubClient> GetClientForLoggedInDeveloper(bool logRateLimit
}

if (logRateLimit)
{
var miscRateLimit = await client.RateLimit.GetRateLimits();
Log.Logger()?.ReportInfo($"Rate Limit: Remaining: {miscRateLimit.Resources.Core.Remaining} Total: {miscRateLimit.Resources.Core.Limit} Resets: {miscRateLimit.Resources.Core.Reset.ToStringInvariant()}");
{
try
{
var miscRateLimit = await client.RateLimit.GetRateLimits();
Log.Logger()?.ReportInfo($"Rate Limit: Remaining: {miscRateLimit.Resources.Core.Remaining} Total: {miscRateLimit.Resources.Core.Limit} Resets: {miscRateLimit.Resources.Core.Reset.ToStringInvariant()}");
}
catch (Exception ex)
{
Log.Logger()?.ReportInfo($"Rate limiting not enabled for server: {ex.Message}");
vineeththomasalex marked this conversation as resolved.
Show resolved Hide resolved
}
}

return client;
Expand Down
12 changes: 12 additions & 0 deletions src/GitHubExtension/Client/Validation.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation and Contributors
// Licensed under the MIT license.
using GitHubExtension.DataModel;
using Octokit;

namespace GitHubExtension.Client;

Expand Down Expand Up @@ -217,4 +218,15 @@ private static string AddProtocolToString(string s)

return n;
}

public static bool IsReachableGitHubEnterpriseServerURL(Uri server)
vineeththomasalex marked this conversation as resolved.
Show resolved Hide resolved
{
if (new EnterpriseProbe(new ProductHeaderValue(Constants.DEV_HOME_APPLICATION_NAME)).Probe(server).Result != EnterpriseProbeResult.Ok)
vineeththomasalex marked this conversation as resolved.
Show resolved Hide resolved
{
Log.Logger()?.ReportError($"EnterpriseServer {server.AbsoluteUri} is not reachable");
return false;
}

vineeththomasalex marked this conversation as resolved.
Show resolved Hide resolved
return true;
}
}
1 change: 1 addition & 0 deletions src/GitHubExtension/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ internal class Constants
{
#pragma warning disable SA1310 // Field names should not contain underscore
public const string DEV_HOME_APPLICATION_NAME = "DevHome";
public const string GITHUB_COM_URL = "https://api.github.com/";
#pragma warning restore SA1310 // Field names should not contain underscore
}
6 changes: 3 additions & 3 deletions src/GitHubExtension/DataManager/GitHubSearchManger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

using GitHubExtension.Client;
using GitHubExtension.DataManager;
using GitHubExtension.DataModel;

using GitHubExtension.DataModel;
namespace GitHubExtension;

public delegate void SearchManagerResultsAvailableEventHandler(IEnumerable<Octokit.Issue> results, string resultType);
Expand All @@ -31,7 +31,7 @@ public GitHubSearchManager()
Environment.FailFast(e.Message, e);
return null;
}
}
}

public async Task SearchForGitHubIssuesOrPRs(Octokit.SearchIssuesRequest request, string initiator, SearchCategory category, RequestOptions? options = null)
{
Expand Down
2 changes: 1 addition & 1 deletion src/GitHubExtension/DataManager/IGitHubSearchManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT license.

using GitHubExtension.DataManager;

namespace GitHubExtension;

public interface IGitHubSearchManager : IDisposable
Expand Down
1 change: 0 additions & 1 deletion src/GitHubExtension/DataModel/DataObjects/Issue.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation and Contributors
// Licensed under the MIT license.

using System.Globalization;
using Dapper;
using Dapper.Contrib.Extensions;
using GitHubExtension.Helpers;
Expand Down
92 changes: 62 additions & 30 deletions src/GitHubExtension/DeveloperId/CredentialVault.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,33 @@
// Licensed under the MIT license.

using System.ComponentModel;
using System.Net;
using System.Runtime.InteropServices;
using System.Security;
using Octokit;
using Windows.Security.Credentials;
using static GitHubExtension.DeveloperId.CredentialManager;

namespace GitHubExtension.DeveloperId;
internal static class CredentialVault
public class CredentialVault : ICredentialVault
{
private readonly string credentialResourceName;
vineeththomasalex marked this conversation as resolved.
Show resolved Hide resolved

private static class CredentialVaultConfiguration
{
public const string CredResourceName = "GitHubDevHomeExtension";
}

internal static void SaveAccessTokenToVault(string loginId, SecureString? accessToken)
public CredentialVault(string applicationName = "")
{
credentialResourceName = string.IsNullOrEmpty(applicationName) ? CredentialVaultConfiguration.CredResourceName : applicationName;
}

public void SaveCredentials(string loginId, SecureString? accessToken)
{
// Initialize a credential object.
var credential = new CREDENTIAL
{
Type = CRED_TYPE.GENERIC,
TargetName = CredentialVaultConfiguration.CredResourceName + ": " + loginId,
TargetName = credentialResourceName + ": " + loginId,
UserName = loginId,
Persist = (int)CRED_PERSIST.LocalMachine,
AttributeCount = 0,
Expand Down Expand Up @@ -61,29 +66,26 @@ internal static void SaveAccessTokenToVault(string loginId, SecureString? access
}
}

internal static void RemoveAccessTokenFromVault(string loginId)
public PasswordCredential? GetCredentials(string loginId)
{
var targetCredentialToDelete = CredentialVaultConfiguration.CredResourceName + ": " + loginId;
var isCredentialDeleted = CredDelete(targetCredentialToDelete, CRED_TYPE.GENERIC, 0);
if (!isCredentialDeleted)
{
Log.Logger()?.ReportInfo($"Deleting credentials from Credential Manager has failed");
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}

internal static PasswordCredential GetCredentialFromLocker(string loginId)
{
var credentialNameToRetrieve = CredentialVaultConfiguration.CredResourceName + ": " + loginId;
var credentialNameToRetrieve = credentialResourceName + ": " + loginId;
vineeththomasalex marked this conversation as resolved.
Show resolved Hide resolved
var ptrToCredential = IntPtr.Zero;

try
{
var isCredentialRetrieved = CredRead(credentialNameToRetrieve, CRED_TYPE.GENERIC, 0, out ptrToCredential);
if (!isCredentialRetrieved)
{
Log.Logger()?.ReportInfo($"Retrieving credentials from Credential Manager has failed");
throw new Win32Exception(Marshal.GetLastWin32Error());
var error = Marshal.GetLastWin32Error();
Log.Logger()?.ReportError($"Retrieving credentials from Credential Manager has failed for {loginId} with {error}");

// NotFound is expected and can be ignored.
if (error == 1168)
vineeththomasalex marked this conversation as resolved.
Show resolved Hide resolved
{
return null;
}

throw new Win32Exception(error);
}

CREDENTIAL credentialObject;
Expand All @@ -96,27 +98,30 @@ internal static PasswordCredential GetCredentialFromLocker(string loginId)
}
else
{
Log.Logger()?.ReportInfo("No credentials found for this DeveloperId");
throw new ArgumentOutOfRangeException(loginId);
Log.Logger()?.ReportError($"No credentials found for this DeveloperId : {loginId}");
return null;
}

var accessTokenInChars = new char[credentialObject.CredentialBlobSize / 2];
Marshal.Copy(credentialObject.CredentialBlob, accessTokenInChars, 0, accessTokenInChars.Length);

var accessToken = new SecureString();
// convert accessTokenInChars to string
string accessTokenString = new (accessTokenInChars);

for (var i = 0; i < accessTokenInChars.Length; i++)
{
accessToken.AppendChar(accessTokenInChars[i]);

// Zero out characters after they are copied over from an unmanaged to managed type.
accessTokenInChars[i] = '\0';
}

accessToken.MakeReadOnly();

var credential = new PasswordCredential(CredentialVaultConfiguration.CredResourceName, loginId, new NetworkCredential(string.Empty, accessToken).Password);
var credential = new PasswordCredential(credentialResourceName, loginId, accessTokenString);
return credential;
}
catch (Exception ex)
{
Log.Logger()?.ReportError($"Retrieving credentials from Credential Manager has failed unexpectedly: {loginId} : {ex.Message}");
throw;
}
finally
{
if (ptrToCredential != IntPtr.Zero)
Expand All @@ -126,7 +131,18 @@ internal static PasswordCredential GetCredentialFromLocker(string loginId)
}
}

public static IEnumerable<string> GetAllSavedLoginIds()
public void RemoveCredentials(string loginId)
vineeththomasalex marked this conversation as resolved.
Show resolved Hide resolved
{
var targetCredentialToDelete = credentialResourceName + ": " + loginId;
var isCredentialDeleted = CredDelete(targetCredentialToDelete, CRED_TYPE.GENERIC, 0);
if (!isCredentialDeleted)
{
Log.Logger()?.ReportError($"Deleting credentials from Credential Manager has failed for {loginId}");
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}

public IEnumerable<string> GetAllCredentials()
{
var ptrToCredential = IntPtr.Zero;

Expand All @@ -135,7 +151,7 @@ public static IEnumerable<string> GetAllSavedLoginIds()
IntPtr[] allCredentials;
uint count;

if (CredEnumerate(CredentialVaultConfiguration.CredResourceName + "*", 0, out count, out ptrToCredential) != false)
if (CredEnumerate(credentialResourceName + "*", 0, out count, out ptrToCredential) != false)
{
allCredentials = new IntPtr[count];
Marshal.Copy(ptrToCredential, allCredentials, 0, (int)count);
Expand Down Expand Up @@ -179,4 +195,20 @@ public static IEnumerable<string> GetAllSavedLoginIds()
}
}
}

public void RemoveAllCredentials()
{
var allCredentials = GetAllCredentials();
foreach (var credential in allCredentials)
{
try
{
RemoveCredentials(credential);
}
catch (Exception ex)
{
Log.Logger()?.ReportError($"Deleting credentials from Credential Manager has failed unexpectedly: {credential} : {ex.Message}");
}
}
}
}
5 changes: 3 additions & 2 deletions src/GitHubExtension/DeveloperId/DeveloperId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,16 @@ public Windows.Security.Credentials.PasswordCredential GetCredential(bool refres
return RefreshDeveloperId();
}

return CredentialVault.GetCredentialFromLocker(LoginId);
var credential = DeveloperIdProvider.GetInstance().GetCredentials(this) ?? throw new InvalidOperationException("Invalid credential present for valid DeveloperId");
return credential;
}

public Windows.Security.Credentials.PasswordCredential RefreshDeveloperId()
{
// Setting to MaxValue, since GitHub doesn't forcibly expire tokens currently.
CredentialExpiryTime = DateTime.MaxValue;
DeveloperIdProvider.GetInstance().RefreshDeveloperId(this);
var credential = CredentialVault.GetCredentialFromLocker(LoginId);
var credential = DeveloperIdProvider.GetInstance().GetCredentials(this) ?? throw new InvalidOperationException("Invalid credential present for valid DeveloperId");
vineeththomasalex marked this conversation as resolved.
Show resolved Hide resolved
GitHubClient.Credentials = new (credential.Password);
return credential;
}
Expand Down
Loading