Skip to content
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
7 changes: 6 additions & 1 deletion examples/Resolver.Athena.CliClient/ClassifyCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ public static async Task<int> DoClassifyCommand(ParseResult parseResult, Cancell
CliUtilities.LoadDotEnv(parseResult);

var svcs = new ServiceCollection()
.AddAthenaClient(CliUtilities.ConfigureAthenaClientFromEnv, CliUtilities.ConfigureOAuthTokenManagerFromEnv)
.AddAthenaClient(o =>
{
CliUtilities.ConfigureAthenaClientFromEnv(o);
o.UnsafeAllowInsecure = parseResult.GetValue(CliUtilities.UnsafeAllowInsecure);
},
CliUtilities.ConfigureOAuthTokenManagerFromEnv)
.BuildServiceProvider();

var athenaClient = svcs.GetRequiredService<IAthenaClient>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ public static async Task<int> DoClassifyDataflowCommand(ParseResult parseResult,
CliUtilities.LoadDotEnv(parseResult);

var svcs = new ServiceCollection()
.AddAthenaDataflowClient(CliUtilities.ConfigureAthenaClientFromEnv, CliUtilities.ConfigureOAuthTokenManagerFromEnv)
.AddAthenaDataflowClient(o =>
{
CliUtilities.ConfigureAthenaClientFromEnv(o);
o.UnsafeAllowInsecure = parseResult.GetValue(CliUtilities.UnsafeAllowInsecure);
},
CliUtilities.ConfigureOAuthTokenManagerFromEnv)
.BuildServiceProvider();

var athenaClient = svcs.GetRequiredService<IAthenaDataflowClient>();
Expand Down
7 changes: 6 additions & 1 deletion examples/Resolver.Athena.CliClient/ClassifySingleCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ public static async Task<int> DoClassifySingleCommand(ParseResult parseResult, C
{
CliUtilities.LoadDotEnv(parseResult);
var svcs = new ServiceCollection()
.AddAthenaClient(CliUtilities.ConfigureAthenaClientFromEnv, CliUtilities.ConfigureOAuthTokenManagerFromEnv)
.AddAthenaClient(o =>
{
CliUtilities.ConfigureAthenaClientFromEnv(o);
o.UnsafeAllowInsecure = parseResult.GetValue(CliUtilities.UnsafeAllowInsecure);
},
CliUtilities.ConfigureOAuthTokenManagerFromEnv)
.BuildServiceProvider();

var athenaClient = svcs.GetRequiredService<IAthenaClient>();
Expand Down
7 changes: 7 additions & 0 deletions examples/Resolver.Athena.CliClient/CliUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ public static partial class CliUtilities
DefaultValueFactory = _ => 0,
};

public static readonly Option<bool> UnsafeAllowInsecure = new("--unsafe-insecure")
{
Description = "If set, allows insecure connections to the Athena endpoint. For development use only.",
Comment thread
anna-singleton-resolver marked this conversation as resolved.
DefaultValueFactory = _ => false,
Recursive = true,
};


Comment thread
anna-singleton-resolver marked this conversation as resolved.
[GeneratedRegex("[^A-Za-z0-9_-]")]
private static partial Regex CorrelationIdRegex();
Expand Down
7 changes: 6 additions & 1 deletion examples/Resolver.Athena.CliClient/ListDeploymentsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ public static async Task<int> DoListDeploymentsCommand(ParseResult parseResult,
CliUtilities.LoadDotEnv(parseResult);

var svcs = new ServiceCollection()
.AddAthenaClient(CliUtilities.ConfigureAthenaClientFromEnv, CliUtilities.ConfigureOAuthTokenManagerFromEnv)
.AddAthenaClient(o =>
{
CliUtilities.ConfigureAthenaClientFromEnv(o);
o.UnsafeAllowInsecure = parseResult.GetValue(CliUtilities.UnsafeAllowInsecure);
},
CliUtilities.ConfigureOAuthTokenManagerFromEnv)
.BuildServiceProvider();

var athenaClient = svcs.GetRequiredService<IAthenaClient>();
Expand Down
1 change: 1 addition & 0 deletions examples/Resolver.Athena.CliClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public static async Task Main(string[] args)

// reuse the pre-defined static option so LoadDotEnv can access the same option
rootCommand.Options.Add(CliUtilities.DotenvPathOption);
rootCommand.Options.Add(CliUtilities.UnsafeAllowInsecure);

TokenTestCommand.RegisterCommand(rootCommand);
ListDeploymentsCommand.RegisterCommand(rootCommand);
Expand Down
9 changes: 7 additions & 2 deletions src/Resolver.Athena.Client/ApiClient/AthenaApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,20 @@ public AthenaApiClient(ITokenManager tokenManager, IOptions<AthenaApiClientConfi
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(clientFactory);

var credentialType = options.Value.UnsafeAllowInsecure
? ChannelCredentials.Insecure
: new SslCredentials();

Comment thread
anna-singleton-resolver marked this conversation as resolved.
var channelOptions = new GrpcChannelOptions
{
Credentials = ChannelCredentials.Create(
new SslCredentials(),
credentialType,
CallCredentials.FromInterceptor(async (context, metadata) =>
{
var token = await tokenManager.GetTokenAsync(context.CancellationToken).ConfigureAwait(false);
metadata.Add("Authorization", $"Bearer {token}");
}))
})),
UnsafeUseInsecureChannelCallCredentials = options.Value.UnsafeAllowInsecure
};
Comment thread
anna-singleton-resolver marked this conversation as resolved.

_client = clientFactory.Create(options.Value.Endpoint, channelOptions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,13 @@ public class AthenaApiClientConfiguration
/// Indicates whether to send SHA1 hashes of images.
/// </summary>
public bool SendSha1Hash { get; set; }

/// <summary>
/// Indicates whether to allow insecure connections.
/// </summary>
/// <remarks>
/// This is obviously unsuitable for production use, and is intended for
/// local development and testing only.
Comment thread
anna-singleton-resolver marked this conversation as resolved.
/// </remarks>
public bool UnsafeAllowInsecure { get; set; }
}
77 changes: 70 additions & 7 deletions test/Resolver.Athena.Tests/Client/AthenaApiClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Resolver.Athena.Tests.Client;
public class AthenaApiClientTests()
{
private readonly List<ClassifyRequest> _sentRequests = [];
private GrpcChannelOptions? _capturedOptions = null;

[Fact]
public async Task SingleSendAndReceiveAsync()
Expand Down Expand Up @@ -161,6 +162,62 @@ public async Task MultipleImagesInMultipleRequestsSendAndReceiveAsync()
}
}

[Fact]
public void ChannelIsSecure_ByDefault()
{
var opts = new AthenaApiClientConfiguration
{
Endpoint = "https://mock-endpoint",
Affiliate = "test-affiliate",
SendMd5Hash = true,
SendSha1Hash = true,
};

CreateTestApiClient([], opts);

Assert.NotNull(_capturedOptions);
Assert.NotNull(_capturedOptions.Credentials);
Assert.False(_capturedOptions.UnsafeUseInsecureChannelCallCredentials);
}

[Fact]
public void ChannelIsSecure_WhenExplicitlySet()
{
var opts = new AthenaApiClientConfiguration
{
Endpoint = "https://mock-endpoint",
Affiliate = "test-affiliate",
SendMd5Hash = true,
SendSha1Hash = true,
UnsafeAllowInsecure = false
};

CreateTestApiClient([], opts);

Assert.NotNull(_capturedOptions);
Assert.NotNull(_capturedOptions.Credentials);
Assert.False(_capturedOptions.UnsafeUseInsecureChannelCallCredentials);
}

[Fact]
public void ChannelIsInsecure_WhenExplicitlySet()
{
var opts = new AthenaApiClientConfiguration
{
Endpoint = "https://mock-endpoint",
Affiliate = "test-affiliate",
SendMd5Hash = true,
SendSha1Hash = true,
UnsafeAllowInsecure = true
};
Comment thread
anna-singleton-resolver marked this conversation as resolved.

CreateTestApiClient([], opts);

Assert.NotNull(_capturedOptions);
Assert.NotNull(_capturedOptions.Credentials);
Assert.True(_capturedOptions.UnsafeUseInsecureChannelCallCredentials);
}

private static async Task<Channel<ClassifyRequest>> CreateChannelWithDataAsync(IEnumerable<ClassifyRequest> requests)
{
var channel = Channel.CreateBounded<ClassifyRequest>(new BoundedChannelOptions(1024)
Expand All @@ -179,6 +236,17 @@ private static async Task<Channel<ClassifyRequest>> CreateChannelWithDataAsync(I
}

private AthenaApiClient CreateTestApiClient(IEnumerable<ClassifyResponse> responses)
{
return CreateTestApiClient(responses, new AthenaApiClientConfiguration
{
Endpoint = "https://mock-endpoint",
Affiliate = "test-affiliate",
SendMd5Hash = true,
SendSha1Hash = true,
});
}

private AthenaApiClient CreateTestApiClient(IEnumerable<ClassifyResponse> responses, AthenaApiClientConfiguration config)
{
var fakeRequestStream = new Mock<IClientStreamWriter<ClassifyRequest>>();
fakeRequestStream.Setup(s => s.WriteAsync(It.IsAny<ClassifyRequest>(), It.IsAny<CancellationToken>()))
Expand All @@ -198,19 +266,14 @@ private AthenaApiClient CreateTestApiClient(IEnumerable<ClassifyResponse> respon
mockClient.Setup(c => c.Classify(It.IsAny<Metadata>(), It.IsAny<DateTime?>(), It.IsAny<CancellationToken>()))
.Returns(duplex);

var opts = new OptionsWrapper<AthenaApiClientConfiguration>(new AthenaApiClientConfiguration
{
Endpoint = "https://mock-endpoint",
Affiliate = "test-affiliate",
SendMd5Hash = true,
SendSha1Hash = true,
});
var opts = new OptionsWrapper<AthenaApiClientConfiguration>(config);
var tokenManagerMock = new Mock<ITokenManager>();
tokenManagerMock.Setup(tm => tm.GetTokenAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync("mock-token");

var factory = new Mock<IAthenaClassifierServiceClientFactory>();
factory.Setup(f => f.Create(It.IsAny<string>(), It.IsAny<GrpcChannelOptions>()))
.Callback((string _, GrpcChannelOptions o) => _capturedOptions = o)
.Returns(mockClient.Object);

return new AthenaApiClient(tokenManagerMock.Object, opts, factory.Object);
Expand Down
Loading