Skip to content
Merged
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,7 @@ tools/*
coverage-results/*

# Rider
**/.idea/*
**/.idea/*

# macOS
.DS_Store
8 changes: 7 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,11 @@
"files.insertFinalNewline": true,
"editor.detectIndentation": false,
"editor.tabSize": 2,
"editor.insertSpaces": true
"editor.insertSpaces": true,
"[csharp]": {
"editor.tabSize": 4
},
"explorer.fileNesting.patterns": {
"*.cs": "I${capture}.cs",
},
}
5 changes: 5 additions & 0 deletions Octokit.Reactive/Clients/IObservableMetaClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ namespace Octokit.Reactive
/// </remarks>
public interface IObservableMetaClient
{
/// <summary>
/// Returns a client to get public keys for validating request signatures.
/// </summary>
IObservablePublicKeysClient PublicKeys { get; }

/// <summary>
/// Retrieves information about GitHub.com, the service or a GitHub Enterprise installation.
/// </summary>
Expand Down
20 changes: 20 additions & 0 deletions Octokit.Reactive/Clients/IObservablePublicKeysClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;

namespace Octokit.Reactive
{
/// <summary>
/// A client for GitHub's meta public keys API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/code-security/secret-scanning/secret-scanning-partner-program#implement-signature-verification-in-your-secret-alert-service">Secret scanning documentation</a> for more details.
/// </remarks>
public interface IObservablePublicKeysClient
{
/// <summary>
/// Retrieves public keys for validating request signatures.
/// </summary>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>An <see cref="MetaPublicKeys"/> containing public keys for validating request signatures.</returns>
IObservable<MetaPublicKeys> Get(PublicKeyType keysType);
}
}
7 changes: 7 additions & 0 deletions Octokit.Reactive/Clients/ObservableMetaClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,16 @@ public ObservableMetaClient(IGitHubClient client)
{
Ensure.ArgumentNotNull(client, nameof(client));

PublicKeys = new ObservablePublicKeysClient(client);

_client = client.Meta;
}

/// <summary>
/// Returns a client to manage get public keys for validating request signatures.
/// </summary>
public IObservablePublicKeysClient PublicKeys { get; private set; }

/// <summary>
/// Retrieves information about GitHub.com, the service or a GitHub Enterprise installation.
/// </summary>
Expand Down
34 changes: 34 additions & 0 deletions Octokit.Reactive/Clients/ObservablePublicKeysClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;

namespace Octokit.Reactive
{
/// <summary>
/// A client for GitHub's public keys API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/code-security/secret-scanning/secret-scanning-partner-program#implement-signature-verification-in-your-secret-alert-service">Secret scanning documentation</a> for more details.
/// </remarks>
public class ObservablePublicKeysClient : IObservablePublicKeysClient
{
private readonly IPublicKeysClient _client;

public ObservablePublicKeysClient(IGitHubClient client)
{
Ensure.ArgumentNotNull(client, nameof(client));

_client = client.Meta.PublicKeys;
}

/// <summary>
/// Retrieves public keys for validating request signatures.
/// </summary>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>An <see cref="MetaPublicKeys"/> containing public keys for validating request signatures.</returns>
public IObservable<MetaPublicKeys> Get(PublicKeyType keysType)
{
return _client.Get(keysType).ToObservable();
}
}
}
28 changes: 28 additions & 0 deletions Octokit.Tests.Integration/Clients/PublicKeysClientTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Threading.Tasks;
using Xunit;

namespace Octokit.Tests.Integration.Clients
{
public class PublicKeysClientTests
{
public class TheGetMethod
{
[IntegrationTest]
public async Task CanRetrievePublicKeys()
{
var github = Helper.GetAnonymousClient();

var result = await github.Meta.PublicKeys.Get(PublicKeyType.SecretScanning);

Assert.NotNull(result);
Assert.Equal(2, result.PublicKeys.Count);

Assert.NotNull(result.PublicKeys[0].KeyIdentifier);
Assert.NotNull(result.PublicKeys[0].Key);

Assert.NotNull(result.PublicKeys[1].KeyIdentifier);
Assert.NotNull(result.PublicKeys[1].Key);
}
}
}
}
90 changes: 90 additions & 0 deletions Octokit.Tests/Clients/PublicKeysClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Threading.Tasks;
using NSubstitute;
using Xunit;

namespace Octokit.Tests.Clients
{
public class PublicKeysClientTests
{
public class TheCtor
{
[Fact]
public void EnsuresNonNullArguments()
{
Assert.Throws<ArgumentNullException>(() => new PublicKeysClient(null));
}
}

public class TheGetMethod
{
[Fact]
public async Task RequestsTheCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new PublicKeysClient(connection);

await client.Get(PublicKeyType.CopilotApi);

connection.Received()
.Get<MetaPublicKeys>(Arg.Is<Uri>(u => u.ToString() == "meta/public_keys/copilot_api"));
}

[Fact]
public async Task RequestsCopilotApiPublicKeysEndpoint()
{
var publicKeys = new MetaPublicKeys(publicKeys: new[] {
new MetaPublicKey("4fe6b016179b74078ade7581abf4e84fb398c6fae4fb973972235b84fcd70ca3", "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELPuPiLVQbHY/clvpNnY+0BzYIXgo\nS0+XhEkTWUZEEznIVpS3rQseDTG6//gEWr4j9fY35+dGOxwOx3Z9mK3i7w==\n-----END PUBLIC KEY-----\n", true),
new MetaPublicKey("df3454252d91570ae1bc597182d1183c7a8d42ff0ae96e0f2be4ba278d776546", "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEl5xbyr5bmETCJzqAvDnYl1ZKJrkf\n89Nyq5j06TTKrnHXXDw4FYNY1uF2S/w6EOaxbf9BxOidCLvjJ8ZgKzNpww==\n-----END PUBLIC KEY-----\n", false)
});

var apiConnection = Substitute.For<IApiConnection>();
apiConnection.Get<MetaPublicKeys>(Arg.Is<Uri>(u => u.ToString() == "meta/public_keys/copilot_api")).Returns(Task.FromResult(publicKeys));

var client = new PublicKeysClient(apiConnection);

var result = await client.Get(PublicKeyType.CopilotApi);

Assert.Equal(2, result.PublicKeys.Count);
Assert.Equal("4fe6b016179b74078ade7581abf4e84fb398c6fae4fb973972235b84fcd70ca3", result.PublicKeys[0].KeyIdentifier);
Assert.Equal("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELPuPiLVQbHY/clvpNnY+0BzYIXgo\nS0+XhEkTWUZEEznIVpS3rQseDTG6//gEWr4j9fY35+dGOxwOx3Z9mK3i7w==\n-----END PUBLIC KEY-----\n", result.PublicKeys[0].Key);
Assert.True(result.PublicKeys[0].IsCurrent);

Assert.Equal("df3454252d91570ae1bc597182d1183c7a8d42ff0ae96e0f2be4ba278d776546", result.PublicKeys[1].KeyIdentifier);
Assert.Equal("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEl5xbyr5bmETCJzqAvDnYl1ZKJrkf\n89Nyq5j06TTKrnHXXDw4FYNY1uF2S/w6EOaxbf9BxOidCLvjJ8ZgKzNpww==\n-----END PUBLIC KEY-----\n", result.PublicKeys[1].Key);
Assert.False(result.PublicKeys[1].IsCurrent);

apiConnection.Received()
.Get<MetaPublicKeys>(Arg.Is<Uri>(u => u.ToString() == "meta/public_keys/copilot_api"));
}

[Fact]
public async Task RequestSecretScanningPublicKeysEndpoint()
{
var publicKeys = new MetaPublicKeys(publicKeys: new[] {
new MetaPublicKey("90a421169f0a406205f1563a953312f0be898d3c7b6c06b681aa86a874555f4a", "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9MJJHnMfn2+H4xL4YaPDA4RpJqUq\nkCmRCBnYERxZanmcpzQSXs1X/AljlKkbJ8qpVIW4clayyef9gWhFbNHWAA==\n-----END PUBLIC KEY-----\n", false),
new MetaPublicKey("bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c", "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYAGMWO8XgCamYKMJS6jc/qgvSlAd\nAjPuDPRcXU22YxgBrz+zoN19MzuRyW87qEt9/AmtoNP5GrobzUvQSyJFVw==\n-----END PUBLIC KEY-----\n", true)
});

var apiConnection = Substitute.For<IApiConnection>();
apiConnection.Get<MetaPublicKeys>(Arg.Is<Uri>(u => u.ToString() == "meta/public_keys/secret_scanning")).Returns(Task.FromResult(publicKeys));

var client = new PublicKeysClient(apiConnection);

var result = await client.Get(PublicKeyType.SecretScanning);

Assert.Equal(2, result.PublicKeys.Count);
Assert.Equal("90a421169f0a406205f1563a953312f0be898d3c7b6c06b681aa86a874555f4a", result.PublicKeys[0].KeyIdentifier);
Assert.Equal("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9MJJHnMfn2+H4xL4YaPDA4RpJqUq\nkCmRCBnYERxZanmcpzQSXs1X/AljlKkbJ8qpVIW4clayyef9gWhFbNHWAA==\n-----END PUBLIC KEY-----\n", result.PublicKeys[0].Key);
Assert.False(result.PublicKeys[0].IsCurrent);

Assert.Equal("bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c", result.PublicKeys[1].KeyIdentifier);
Assert.Equal("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYAGMWO8XgCamYKMJS6jc/qgvSlAd\nAjPuDPRcXU22YxgBrz+zoN19MzuRyW87qEt9/AmtoNP5GrobzUvQSyJFVw==\n-----END PUBLIC KEY-----\n", result.PublicKeys[1].Key);
Assert.True(result.PublicKeys[1].IsCurrent);

apiConnection.Received()
.Get<MetaPublicKeys>(Arg.Is<Uri>(u => u.ToString() == "meta/public_keys/secret_scanning"));
}
}
}
}
44 changes: 44 additions & 0 deletions Octokit.Tests/Models/MetaPublicKeysTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Octokit.Internal;
using Xunit;

namespace Octokit.Tests.Models
{
public class MetaPublicKeysTests
{
[Fact]
public void CanBeDeserialized()
{
const string json = @"{
""public_keys"": [
{
""key_identifier"": ""90a421169f0a406205f1563a953312f0be898d3c7b6c06b681aa86a874555f4a"",
""key"": ""-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9MJJHnMfn2+H4xL4YaPDA4RpJqUq\nkCmRCBnYERxZanmcpzQSXs1X/AljlKkbJ8qpVIW4clayyef9gWhFbNHWAA==\n-----END PUBLIC KEY-----\n"",
""is_current"": false
},
{
""key_identifier"": ""bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c"",
""key"": ""-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYAGMWO8XgCamYKMJS6jc/qgvSlAd\nAjPuDPRcXU22YxgBrz+zoN19MzuRyW87qEt9/AmtoNP5GrobzUvQSyJFVw==\n-----END PUBLIC KEY-----\n"",
""is_current"": true
}
]
}
";
var serializer = new SimpleJsonSerializer();

var keys = serializer.Deserialize<MetaPublicKeys>(json);

Assert.NotNull(keys);
Assert.Equal(2, keys.PublicKeys.Count);

var key1 = keys.PublicKeys[0];
Assert.Equal("90a421169f0a406205f1563a953312f0be898d3c7b6c06b681aa86a874555f4a", key1.KeyIdentifier);
Assert.Equal("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9MJJHnMfn2+H4xL4YaPDA4RpJqUq\nkCmRCBnYERxZanmcpzQSXs1X/AljlKkbJ8qpVIW4clayyef9gWhFbNHWAA==\n-----END PUBLIC KEY-----\n", key1.Key);
Assert.False(key1.IsCurrent);

var key2 = keys.PublicKeys[1];
Assert.Equal("bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c", key2.KeyIdentifier);
Assert.Equal("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYAGMWO8XgCamYKMJS6jc/qgvSlAd\nAjPuDPRcXU22YxgBrz+zoN19MzuRyW87qEt9/AmtoNP5GrobzUvQSyJFVw==\n-----END PUBLIC KEY-----\n", key2.Key);
Assert.True(key2.IsCurrent);
}
}
}
33 changes: 33 additions & 0 deletions Octokit.Tests/Reactive/ObservablePublicKeysClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using NSubstitute;
using Octokit.Reactive;
using Xunit;

namespace Octokit.Tests.Reactive
{
public class ObservablePublicKeysClientTests
{
public class TheGetMethod
{
[Fact]
public void CallsIntoClient()
{
var gitHubClient = Substitute.For<IGitHubClient>();
var client = new ObservablePublicKeysClient(gitHubClient);

client.Get(PublicKeyType.SecretScanning);

gitHubClient.Meta.PublicKeys.Received(1).Get(PublicKeyType.SecretScanning);
}
}

public class TheCtor
{
[Fact]
public void EnsuresNonNullArguments()
{
Assert.Throws<ArgumentNullException>(() => new ObservablePublicKeysClient((IGitHubClient)null));
}
}
}
}
5 changes: 5 additions & 0 deletions Octokit/Clients/IMetaClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ namespace Octokit
/// </remarks>
public interface IMetaClient
{
/// <summary>
/// Returns a client to get public keys for validating request signatures.
/// </summary>
IPublicKeysClient PublicKeys { get; }

/// <summary>
/// Retrieves information about GitHub.com, the service or a GitHub Enterprise installation.
/// </summary>
Expand Down
20 changes: 20 additions & 0 deletions Octokit/Clients/IPublicKeysClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Threading.Tasks;

namespace Octokit
{
/// <summary>
/// A client for GitHub's meta public keys API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/code-security/secret-scanning/secret-scanning-partner-program#implement-signature-verification-in-your-secret-alert-service">Secret scanning documentation</a> for more details.
/// </remarks>
public interface IPublicKeysClient
{
/// <summary>
/// Retrieves public keys for validating request signatures.
/// </summary>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>An <see cref="MetaPublicKeys"/> containing public keys for validating request signatures.</returns>
Task<MetaPublicKeys> Get(PublicKeyType keysType);
}
}
6 changes: 6 additions & 0 deletions Octokit/Clients/MetaClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@ public class MetaClient : ApiClient, IMetaClient
public MetaClient(IApiConnection apiConnection)
: base(apiConnection)
{
PublicKeys = new PublicKeysClient(apiConnection);
}

/// <summary>
/// Returns a client to manage get public keys for validating request signatures.
/// </summary>
public IPublicKeysClient PublicKeys { get; private set; }

/// <summary>
/// Retrieves information about GitHub.com, the service or a GitHub Enterprise installation.
/// </summary>
Expand Down
33 changes: 33 additions & 0 deletions Octokit/Clients/PublicKeysClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Threading.Tasks;

namespace Octokit
{
/// <summary>
/// A client for GitHub's public keys API.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/code-security/secret-scanning/secret-scanning-partner-program#implement-signature-verification-in-your-secret-alert-service">Secret scanning documentation</a> for more details.
/// </remarks>
public class PublicKeysClient : ApiClient, IPublicKeysClient
{
/// <summary>
/// Initializes a new GitHub Meta Public Keys API client.
/// </summary>
/// <param name="apiConnection">An API connection.</param>
public PublicKeysClient(IApiConnection apiConnection)
: base(apiConnection)
{
}

/// <summary>
/// Retrieves public keys for validating request signatures.
/// </summary>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>An <see cref="MetaPublicKeys"/> containing public keys for validating request signatures.</returns>
[ManualRoute("GET", "/meta/public_keys/{keysType}")]
public Task<MetaPublicKeys> Get(PublicKeyType keysType)
{
return ApiConnection.Get<MetaPublicKeys>(ApiUrls.PublicKeys(keysType));
}
}
}
Loading