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

Upgrade to .NET 9 ++ #138

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ dotnet_style_qualification_for_event = false:suggestion

# Types: use keywords instead of BCL types, and permit var only when the type is clear
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = false:none
csharp_style_var_when_type_is_apparent = true:none
csharp_style_var_elsewhere = false:suggestion
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:

- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.x.x'
dotnet-version: '9.x.x'

- name: Build the project
run: dotnet build src/GitHub.Octokit.SDK.csproj
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x.x
dotnet-version: 9.x.x

- name: Build and strong name
run: dotnet build --configuration Release --no-incremental -p:version=${GITHUB_REF#refs/*/v} -p:SignAssembly=true -p:AssemblyOriginatorKeyFile=../key.snk
Expand Down
11 changes: 6 additions & 5 deletions GitHub.Octokit.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Microsoft Visual Studio Solution File, Format Version 12.00

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Expand All @@ -8,7 +9,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
.github\workflows\add_to_octokit_project.yml = .github\workflows\add_to_octokit_project.yml
.github\workflows\build.yml = .github\workflows\build.yml
.github\workflows\immediate-response.yml = .github\workflows\immediate-response.yml
LICENSE = LICENSE
.github\workflows\publish.yml = .github\workflows\publish.yml
README.md = README.md
EndProjectSection
EndProject
Expand All @@ -28,10 +33,6 @@ Global
{7EF0200D-9F10-4A77-8F73-3E26D3EBD326}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7EF0200D-9F10-4A77-8F73-3E26D3EBD326}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7EF0200D-9F10-4A77-8F73-3E26D3EBD326}.Release|Any CPU.Build.0 = Release|Any CPU
{3A6900D7-23D6-4AE5-B76A-76A05CD336F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3A6900D7-23D6-4AE5-B76A-76A05CD336F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3A6900D7-23D6-4AE5-B76A-76A05CD336F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A6900D7-23D6-4AE5-B76A-76A05CD336F1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ using GitHub.Octokit.Client.Authentication;

var tokenProvider = new TokenProvider(Environment.GetEnvironmentVariable("GITHUB_TOKEN") ?? "");
var adapter = RequestAdapter.Create(new TokenAuthProvider(tokenProvider));
await MakeRequest(new GitHubClient(adapter));
var gitHubClient = new GitHubClient(adapter);

try
{
Expand Down Expand Up @@ -84,6 +84,8 @@ var adapter = new ClientFactory()
.WithBaseUrl("https://api.github.com")
.Build();

var gitHubClient = new GitHubClient(adapter);

try
{
var response = await gitHubClient.Repositories.GetAsync();
Expand All @@ -102,21 +104,21 @@ This SDK supports [Personal Access Tokens (classic)](https://docs.github.com/en/
In order to use either type of Personal Access token, you can use the `TokenAuthProvider` constructor option when constructing a new token provider, like so:

```csharp
var tokenProvider = new TokenProvider(Environment.GetEnvironmentVariable("GITHUB_TOKEN") ?? "");
var adapter = RequestAdapter.Create(new TokenAuthProvider(tokenProvider));
var gitHubClient = new GitHubClient(adapter);
var tokenProvider = new TokenProvider(Environment.GetEnvironmentVariable("GITHUB_TOKEN") ?? "");
var adapter = RequestAdapter.Create(new TokenAuthProvider(tokenProvider));
var gitHubClient = new GitHubClient(adapter);
```

In order to authenticate as a GitHub App, you can use the `AppInstallationAuthProvider` constructor option:

```csharp
var githubAppTokenProvider = new GitHubAppTokenProvider();
var rsa = RSA.Create();
rsa.ImportFromPem(PRIVATE_KEY_PATH);
var githubAppTokenProvider = new GitHubAppTokenProvider();
var rsa = RSA.Create();
rsa.ImportFromPem(PRIVATE_KEY_PATH);

var aiAccessTokenProvider = new AppInstallationTokenProvider(CLIENT_ID, rsa, INSTALLATION_ID, githubAppTokenProvider);
var aiAdapter = RequestAdapter.Create(new AppInstallationAuthProvider(aiAccessTokenProvider));
var aiGitHubClient = new GitHubClient(aiAdapter);
var aiAccessTokenProvider = new AppInstallationTokenProvider(CLIENT_ID, rsa, INSTALLATION_ID, githubAppTokenProvider);
var aiAdapter = RequestAdapter.Create(new AppInstallationAuthProvider(aiAccessTokenProvider));
var aiGitHubClient = new GitHubClient(aiAdapter);
```

To see more detailed examples, view [the cli/ directory in this repo](cli/).
Expand Down
6 changes: 1 addition & 5 deletions src/Client/Authentication/AppInstallationAuthProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ namespace GitHub.Octokit.Client.Authentication;
/// This is beneficial for dev ergonomics - where BaseBearerTokenAuthenticationProvider
/// is a base class for all authentication providers that use a bearer token
/// </summary>
public class AppInstallationAuthProvider : BaseBearerTokenAuthenticationProvider
public class AppInstallationAuthProvider(IAccessTokenProvider tokenProvider) : BaseBearerTokenAuthenticationProvider(tokenProvider)
{
public AppInstallationAuthProvider(IAccessTokenProvider tokenProvider) : base(tokenProvider)
{

}
}
13 changes: 6 additions & 7 deletions src/Client/Authentication/AppInstallationTokenProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,17 @@ namespace GitHub.Octokit.Client.Authentication;
/// <summary>
/// Used for Github App InstallationToken authentication
/// </summary>
public class AppInstallationTokenProvider : IAccessTokenProvider
public sealed class AppInstallationTokenProvider : IAccessTokenProvider
{
private readonly string _sourceId;
private readonly RSA _privateKey;
private readonly string _installationId;
private string _accessToken = string.Empty;

private SecurityTokenDescriptor? _tokenDescriptor;

private IGitHubAppTokenProvider _gitHubAppTokenProvider;
private readonly string _sourceId;
private readonly RSA _privateKey;
private readonly string _installationId;
private readonly IGitHubAppTokenProvider _gitHubAppTokenProvider;

AllowedHostsValidator IAccessTokenProvider.AllowedHostsValidator => new AllowedHostsValidator();
AllowedHostsValidator IAccessTokenProvider.AllowedHostsValidator => new();

/// <summary>
/// Constructor for AppInstallationTokenProvider using the clientId
Expand Down
38 changes: 23 additions & 15 deletions src/Client/Authentication/GitHubAppTokenProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ namespace GitHub.Octokit.Client.Authentication;
/// <summary>
/// This class is responsible for creating, signing and refreshing access tokens
/// </summary>
public class GitHubAppTokenProvider : IGitHubAppTokenProvider
public sealed class GitHubAppTokenProvider : IGitHubAppTokenProvider
{
private const int _timeoutMinutes = 10;
private const int TimeoutMinutes = 10;

/// <summary>
/// Create a token descriptor
/// We want to store the descriptor so that we can automatically refresh the token before it expires
/// </summary>
/// <param name="privateKey"></param>
/// <param name="sourceId"></param>
/// <param name="issuedAt"></param>
/// <param name="privateKey">The private key belonging to the GitHub App.</param>
/// <param name="sourceId">The issuer for the token descriptor claims.</param>
/// <param name="issuedAt">The DateTime for the token descriptor to be issued at.</param>
/// <returns>A token descriptor that can be used to create a JWT.</returns>
public SecurityTokenDescriptor CreateTokenDescriptor(RSA privateKey, string sourceId, DateTime issuedAt)
{
var signingCredentials = new SigningCredentials(new RsaSecurityKey(privateKey), SecurityAlgorithms.RsaSha256);
Expand All @@ -29,18 +30,25 @@ public SecurityTokenDescriptor CreateTokenDescriptor(RSA privateKey, string sour
Issuer = sourceId,
IssuedAt = issuedAt,
NotBefore = issuedAt,
Expires = issuedAt.AddMinutes(_timeoutMinutes),
Expires = issuedAt.AddMinutes(TimeoutMinutes),
SigningCredentials = signingCredentials,
Claims = new Dictionary<string, object>
{
{ JwtRegisteredClaimNames.Iat, new DateTimeOffset(issuedAt).ToUnixTimeSeconds() },
{ JwtRegisteredClaimNames.Exp, new DateTimeOffset(issuedAt.AddMinutes(_timeoutMinutes)).ToUnixTimeSeconds() },
{ JwtRegisteredClaimNames.Exp, new DateTimeOffset(issuedAt.AddMinutes(TimeoutMinutes)).ToUnixTimeSeconds() },
{ JwtRegisteredClaimNames.Iss, sourceId }
}
};

return tokenDescriptor;
}

/// <summary>
/// Creates a JWT using the given token descriptor. This JWT can be used to authenticate to the endpoints
/// requiring the App to authenticate as itself.
/// </summary>
/// <param name="tokenDescriptor">The token descriptor to use when creating the JWT.</param>
/// <returns>A JWT string that can be used for authentication.</returns>
public string CreateJsonWebToken(SecurityTokenDescriptor tokenDescriptor)
{
var tokenHandler = new JsonWebTokenHandler();
Expand All @@ -50,26 +58,26 @@ public string CreateJsonWebToken(SecurityTokenDescriptor tokenDescriptor)
/// <summary>
/// Get the app installation access token from GitHub
/// </summary>
/// <param name="baseUrl"></param>
/// <param name="jwt"></param>
/// <param name="installationId"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
/// <param name="baseUrl">The base URL of the GitHub API.</param>
/// <param name="jwt">The JSON Web Token for authentication.</param>
/// <param name="installationId">The installation ID of the GitHub App.</param>
/// <returns>An access token that can be used to authenticate to the GitHub API as an App installation.</returns>
/// <exception cref="Exception">Thrown when the access token cannot be retrieved.</exception>
public async Task<string> GetGitHubAccessTokenAsync(string baseUrl, string jwt, string installationId)
{
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, $"{baseUrl}/app/installations/{installationId}/access_tokens");
using var request = new HttpRequestMessage(HttpMethod.Post, $"{baseUrl}/app/installations/{installationId}/access_tokens");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));

var userAgentOptions = new UserAgentOptions();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue(userAgentOptions.ProductName ?? string.Empty, userAgentOptions.ProductVersion));

var response = await client.SendAsync(request);
using var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json);
using var doc = JsonDocument.Parse(json);
var token = doc.RootElement.GetProperty("token").GetString();

return token ?? throw new Exception("Failed to get access token");
Expand Down
16 changes: 8 additions & 8 deletions src/Client/Authentication/TokenAuthProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

namespace GitHub.Octokit.Client.Authentication;


/// <summary>
/// Represents an authentication provider for app installations.
/// This class is a concrete implementation of <see cref="BaseBearerTokenAuthenticationProvider"/>.
/// This is beneficial for dev ergonomics - where BaseBearerTokenAuthenticationProvider
/// is a base class for all authentication providers that use a bearer token
/// This is beneficial for dev ergonomics - where <c>BaseBearerTokenAuthenticationProvider</c>
/// is a base class for all authentication providers that use a bearer token.
/// </summary>
public class TokenAuthProvider : BaseBearerTokenAuthenticationProvider
/// <param name="tokenProvider">
/// The access token provider to forward to
/// the base <see cref="BaseBearerTokenAuthenticationProvider"/>.
/// </param>
public sealed class TokenAuthProvider(
IAccessTokenProvider tokenProvider) : BaseBearerTokenAuthenticationProvider(tokenProvider)
{
public TokenAuthProvider(IAccessTokenProvider tokenProvider) : base(tokenProvider)
{

}
}
24 changes: 14 additions & 10 deletions src/Client/Authentication/TokenProvider.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
using Microsoft.Kiota.Abstractions.Authentication;

namespace GitHub.Octokit.Client.Authentication;

/// <summary>
/// Used for basic token authentication
/// </summary>
public class TokenProvider : IAccessTokenProvider
public sealed class TokenProvider : IAccessTokenProvider
{
private readonly string _accessToken;
AllowedHostsValidator IAccessTokenProvider.AllowedHostsValidator => new AllowedHostsValidator();

/// <inheritdoc />
AllowedHostsValidator IAccessTokenProvider.AllowedHostsValidator => new();

/// <summary>
/// Constructor for TokenProvider using the access token
/// </summary>
/// <param name="accessToken"></param>
/// <exception cref="ArgumentException"></exception>
/// <param name="accessToken">The access token to be used for authentication.</param>
/// <exception cref="ArgumentException">Thrown when the access token is null or empty.</exception>
public TokenProvider(string accessToken)
{
if (string.IsNullOrEmpty(accessToken)) throw new ArgumentException(nameof(accessToken));
ArgumentException.ThrowIfNullOrWhiteSpace(accessToken);

_accessToken = accessToken;
}

/// <summary>
/// Get the authorization token
/// Gets the authorization token.
/// </summary>
/// <param name="requestUri"></param>
/// <param name="additionalAuthenticationContext"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <param name="requestUri">The URI of the request.</param>
/// <param name="additionalAuthenticationContext">Additional context for authentication.</param>
/// <param name="cancellationToken">A token to cancel the operation.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the access token.</returns>
public Task<string> GetAuthorizationTokenAsync(Uri requestUri, Dictionary<string, object>? additionalAuthenticationContext = default, CancellationToken cancellationToken = default)
{
return Task.FromResult(_accessToken);
Expand Down
Loading
Loading