Skip to content

Commit 74eb627

Browse files
feat: Support apiKey for GO Feature Flag relay proxy v1.7.0 (#59)
1 parent 2e4fda3 commit 74eb627

File tree

6 files changed

+87
-9
lines changed

6 files changed

+87
-9
lines changed

src/OpenFeature.Contrib.Providers.GOFeatureFlag/GoFeatureFlagProvider.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Globalization;
34
using System.Net;
45
using System.Net.Http;
56
using System.Net.Http.Headers;
@@ -62,6 +63,10 @@ private void InitializeProvider(GoFeatureFlagProviderOptions options)
6263
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(ApplicationJson));
6364
_httpClient.BaseAddress = new Uri(options.Endpoint);
6465
_serializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
66+
67+
if (options.ApiKey != null)
68+
_httpClient.DefaultRequestHeaders.Authorization =
69+
new AuthenticationHeaderValue("Bearer", options.ApiKey);
6570
}
6671

6772
/// <summary>
@@ -181,7 +186,8 @@ public override async Task<ResolutionDetails<double>> ResolveDoubleValue(string
181186
try
182187
{
183188
var resp = await CallApi(flagKey, defaultValue, context);
184-
return new ResolutionDetails<double>(flagKey, double.Parse(resp.value.ToString(), System.Globalization.CultureInfo.InvariantCulture), ErrorType.None,
189+
return new ResolutionDetails<double>(flagKey,
190+
double.Parse(resp.value.ToString(), CultureInfo.InvariantCulture), ErrorType.None,
185191
resp.reason, resp.variationType);
186192
}
187193
catch (FormatException e)
@@ -257,6 +263,9 @@ private async Task<GoFeatureFlagResponse> CallApi<T>(string flagKey, T defaultVa
257263
if (response.StatusCode == HttpStatusCode.NotFound)
258264
throw new FlagNotFoundError($"flag {flagKey} was not found in your configuration");
259265

266+
if (response.StatusCode == HttpStatusCode.Unauthorized)
267+
throw new UnauthorizedError("invalid token used to contact GO Feature Flag relay proxy instance");
268+
260269
if (response.StatusCode >= HttpStatusCode.BadRequest)
261270
throw new GeneralError("impossible to contact GO Feature Flag relay proxy instance");
262271

src/OpenFeature.Contrib.Providers.GOFeatureFlag/GoFeatureFlagProviderOptions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,14 @@ public class GoFeatureFlagProviderOptions
2525
/// Default: null
2626
/// </Summary>
2727
public HttpMessageHandler HttpMessageHandler { get; set; }
28+
29+
/// <Summary>
30+
/// (optional) If the relay proxy is configured to authenticate the request, you should provide
31+
/// an API Key to the provider.
32+
/// Please ask the administrator of the relay proxy to provide an API Key.
33+
/// (This feature is available only if you are using GO Feature Flag relay proxy v1.7.0 or above)
34+
/// Default: null
35+
/// </Summary>
36+
public string ApiKey { get; set; }
2837
}
2938
}

src/OpenFeature.Contrib.Providers.GOFeatureFlag/GoFeatureFlagUser.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.Collections.Generic;
22
using System.Linq;
3-
43
using OpenFeature.Contrib.Providers.GOFeatureFlag.exception;
54
using OpenFeature.Model;
65

@@ -15,17 +14,17 @@ public class GoFeatureFlagUser
1514
private const string KeyField = "targetingKey";
1615

1716
/// <summary>
18-
/// The targeting key for the user.
17+
/// The targeting key for the user.
1918
/// </summary>
2019
public string Key { get; private set; }
2120

2221
/// <summary>
23-
/// Is the user Anonymous.
22+
/// Is the user Anonymous.
2423
/// </summary>
2524
public bool Anonymous { get; private set; }
2625

2726
/// <summary>
28-
/// Additional Custom Data to pass to GO Feature Flag.
27+
/// Additional Custom Data to pass to GO Feature Flag.
2928
/// </summary>
3029
public Dictionary<string, object> Custom { get; private set; }
3130

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using OpenFeature.Constant;
3+
using OpenFeature.Error;
4+
5+
namespace OpenFeature.Contrib.Providers.GOFeatureFlag.exception
6+
{
7+
/// <summary>
8+
/// Exception throw when we are not authorized to call the API in the relay proxy.
9+
/// </summary>
10+
public class UnauthorizedError : FeatureProviderException
11+
{
12+
/// <summary>
13+
/// Constructor of the exception
14+
/// </summary>
15+
/// <param name="message">Message to display</param>
16+
/// <param name="innerException">Original exception</param>
17+
public UnauthorizedError(string message, Exception innerException = null) : base(ErrorType.General, message,
18+
innerException)
19+
{
20+
}
21+
}
22+
}

test/OpenFeature.Contrib.Providers.GOFeatureFlag.Test/GoFeatureFlagProviderTest.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ private static HttpMessageHandler InitMock()
2424
const string mediaType = "application/json";
2525
var mockHttp = new MockHttpMessageHandler();
2626
mockHttp.When($"{prefixEval}fail_500{suffixEval}").Respond(HttpStatusCode.InternalServerError);
27+
mockHttp.When($"{prefixEval}api_key_missing{suffixEval}").Respond(HttpStatusCode.BadRequest);
28+
mockHttp.When($"{prefixEval}invalid_api_key{suffixEval}").Respond(HttpStatusCode.Unauthorized);
2729
mockHttp.When($"{prefixEval}flag_not_found{suffixEval}").Respond(HttpStatusCode.NotFound);
2830
mockHttp.When($"{prefixEval}bool_targeting_match{suffixEval}").Respond(mediaType,
2931
"{\"trackEvents\":true,\"variationType\":\"True\",\"failed\":false,\"version\":\"\",\"reason\":\"TARGETING_MATCH\",\"errorCode\":\"\",\"value\":true}");
@@ -149,6 +151,43 @@ private void should_throw_an_error_if_endpoint_not_available()
149151
Assert.Equal(Reason.Error, res.Result.Reason);
150152
}
151153

154+
[Fact]
155+
private void should_have_bad_request_if_no_token()
156+
{
157+
var g = new GoFeatureFlagProvider(new GoFeatureFlagProviderOptions
158+
{
159+
Endpoint = baseUrl,
160+
HttpMessageHandler = _mockHttp,
161+
Timeout = new TimeSpan(1000 * TimeSpan.TicksPerMillisecond)
162+
});
163+
Api.Instance.SetProvider(g);
164+
var client = Api.Instance.GetClient("test-client");
165+
var res = client.GetBooleanDetails("api_key_missing", false, _defaultEvaluationCtx);
166+
Assert.NotNull(res.Result);
167+
Assert.False(res.Result.Value);
168+
Assert.Equal(Reason.Error, res.Result.Reason);
169+
Assert.Equal(ErrorType.General, res.Result.ErrorType);
170+
}
171+
172+
[Fact]
173+
private void should_have_unauthorized_if_invalid_token()
174+
{
175+
var g = new GoFeatureFlagProvider(new GoFeatureFlagProviderOptions
176+
{
177+
Endpoint = baseUrl,
178+
HttpMessageHandler = _mockHttp,
179+
Timeout = new TimeSpan(1000 * TimeSpan.TicksPerMillisecond),
180+
ApiKey = "ff877c7a-4594-43b5-89a8-df44c9984bd8"
181+
});
182+
Api.Instance.SetProvider(g);
183+
var client = Api.Instance.GetClient("test-client");
184+
var res = client.GetBooleanDetails("invalid_api_key", false, _defaultEvaluationCtx);
185+
Assert.NotNull(res.Result);
186+
Assert.False(res.Result.Value);
187+
Assert.Equal(Reason.Error, res.Result.Reason);
188+
Assert.Equal(ErrorType.General, res.Result.ErrorType);
189+
}
190+
152191
[Fact]
153192
private void should_throw_an_error_if_flag_does_not_exists()
154193
{

test/OpenFeature.Contrib.Providers.GOFeatureFlag.Test/GoFeatureFlagUserTest.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using System.Text.Json;
2-
32
using OpenFeature.Model;
4-
53
using Xunit;
64

75
namespace OpenFeature.Contrib.Providers.GOFeatureFlag.Test;
@@ -22,8 +20,10 @@ public void GoFeatureFlagUserSerializesCorrectly()
2220

2321
GoFeatureFlagUser user = userContext;
2422

25-
var userAsString = JsonSerializer.Serialize(user, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
23+
var userAsString = JsonSerializer.Serialize(user,
24+
new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
2625

27-
Assert.Contains("{\"key\":\"1d1b9238-2591-4a47-94cf-d2bc080892f1\",\"anonymous\":false,\"custom\":{", userAsString);
26+
Assert.Contains("{\"key\":\"1d1b9238-2591-4a47-94cf-d2bc080892f1\",\"anonymous\":false,\"custom\":{",
27+
userAsString);
2828
}
2929
}

0 commit comments

Comments
 (0)