Skip to content

Commit 10cbd08

Browse files
⚡ feat: bump minor version to add support for APS environment variables (#179)
* Add support for APS alternative environment variables * Add unit tests and support for APS configuration and environment variables * fix review feedback: updated to correct semantic verion 4.1.0
1 parent b5b4065 commit 10cbd08

File tree

6 files changed

+274
-6
lines changed

6 files changed

+274
-6
lines changed

CHANGELOG.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
### 4.1.0
2+
3+
* Add support for APS alternative environment variables `APS_CLIENT_ID` and `APS_CLIENT_SECRET`
4+
* `FORGE_CLIENT_ID` and `FORGE_CLIENT_SECRET` will be deprecated in future!
5+
16
### 4.0.1
27

38
* Add Documentation in the `Autodesk.Forge.Core` project.
@@ -18,18 +23,21 @@
1823

1924
* Migrate to .Net 5
2025
* Support to return HttpStatusCode in HttpRequestException
21-
2226

2327
### 1.0.0.0
2428

2529
* 1.0.0-beta4
26-
* Support concurrency with `SemaphoreSlim`
30+
31+
* Support concurrency with `SemaphoreSlim`
2732

2833
* 1.0.0-beta3
29-
* Configurable timeout
34+
35+
* Configurable timeout
3036

3137
* 1.0.0-beta2
32-
* Fix NuGet package settings
38+
39+
* Fix NuGet package settings
3340

3441
* 1.0.0-beta1
35-
* Support for `FORGE_CLIENT_ID` and `FORGE_CLIENT_SECRET` environment variables via `ForgeAlternativeConfigurationExtensions`
42+
43+
* Support for `FORGE_CLIENT_ID` and `FORGE_CLIENT_SECRET` environment variables via `ForgeAlternativeConfigurationExtensions`

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>4.0.1</Version>
3+
<Version>4.1.0</Version>
44
<TargetFramework>net8.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
</PropertyGroup>

src/Autodesk.Forge.Core/LegacySampleConfigurationProvider.cs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,30 @@ public static class ForgeAlternativeConfigurationExtensions
2929
/// </summary>
3030
/// <param name="configurationBuilder">The configuration builder.</param>
3131
/// <returns>The configuration builder with Forge alternative environment variables added.</returns>
32+
///
33+
[Obsolete("Use AddAPSAlternativeEnvironmentVariables instead will be removed in future release")]
3234
public static IConfigurationBuilder AddForgeAlternativeEnvironmentVariables(this IConfigurationBuilder configurationBuilder)
3335
{
3436
configurationBuilder.Add(new ForgeAlternativeConfigurationSource());
3537
return configurationBuilder;
3638
}
39+
40+
/// <summary>
41+
/// Adds APS alternative environment variables to the configuration builder.
42+
/// </summary>
43+
/// <param name="configurationBuilder"></param>
44+
/// <returns></returns>
45+
46+
public static IConfigurationBuilder AddAPSAlternativeEnvironmentVariables(this IConfigurationBuilder configurationBuilder)
47+
{
48+
configurationBuilder.Add(new APSAlternativeConfigurationSource());
49+
return configurationBuilder;
50+
}
51+
3752
}
3853

54+
55+
3956
/// <summary>
4057
/// Represents a configuration source for loading Forge alternative configuration.
4158
/// </summary>
@@ -74,5 +91,49 @@ public override void Load()
7491
}
7592
}
7693
}
94+
95+
96+
97+
/// <summary>
98+
/// Represents a configuration source for loading APS alternative configuration.
99+
/// </summary>
100+
101+
public class APSAlternativeConfigurationSource : IConfigurationSource
102+
{
103+
/// <summary>
104+
/// Build the APS Environment Configuration Provider
105+
/// </summary>
106+
/// <param name="builder"></param>
107+
/// <returns></returns>
108+
public IConfigurationProvider Build(IConfigurationBuilder builder)
109+
{
110+
return new APSAlternativeConfigurationProvider();
111+
}
112+
}
113+
114+
/// <summary>
115+
/// Loads the APS alternative configuration from environment variables.
116+
/// </summary>
117+
118+
public class APSAlternativeConfigurationProvider : ConfigurationProvider
119+
{
120+
/// <summary>
121+
/// Loads the APS alternative configuration from environment variables.
122+
/// </summary>
123+
public override void Load()
124+
{
125+
var id = Environment.GetEnvironmentVariable("APS_CLIENT_ID");
126+
if (!string.IsNullOrEmpty(id))
127+
{
128+
this.Data.Add("APS:ClientId", id);
129+
}
130+
var secret = Environment.GetEnvironmentVariable("APS_CLIENT_SECRET");
131+
if (!string.IsNullOrEmpty(secret))
132+
{
133+
this.Data.Add("APS:ClientSecret", secret);
134+
}
135+
}
136+
137+
}
77138
}
78139

src/Autodesk.Forge.Core/ServiceCollectionExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public static IHttpClientBuilder AddForgeService(this IServiceCollection service
3737
{
3838
services.AddOptions();
3939
services.Configure<ForgeConfiguration>(configuration.GetSection("Forge"));
40+
services.Configure<ForgeConfiguration>(configuration.GetSection("APS"));
4041
services.AddTransient<ForgeHandler>();
4142
return services.AddHttpClient<ForgeService>()
4243
.AddHttpMessageHandler<ForgeHandler>();
@@ -57,6 +58,7 @@ public static IHttpClientBuilder AddForgeService(this IServiceCollection service
5758
{
5859
services.AddOptions();
5960
services.Configure<ForgeConfiguration>(configuration.GetSection("Forge"));
61+
services.Configure<ForgeConfiguration>(configuration.GetSection("APS"));
6062
services.AddTransient<ForgeHandler>();
6163
return services.AddHttpClient<ForgeService>(user)
6264
.AddHttpMessageHandler(() => new ForgeAgentHandler(user))
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
using Microsoft.Extensions.Configuration;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.Options;
4+
using Moq;
5+
using Moq.Protected;
6+
using Newtonsoft.Json;
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
using System.Text;
11+
using System.Threading.Tasks;
12+
using Xunit;
13+
14+
namespace Autodesk.Forge.Core.Test
15+
{
16+
public class TestAPSConfiguration
17+
{
18+
/// <summary>
19+
/// Tests using APS__ClientId and APS__ClientSecret environment variables dotnet core style
20+
/// </summary>
21+
[Fact]
22+
public void TestAPSConfigFromEnvironmentVariables_DoubleUnderscoreFormat()
23+
{
24+
Environment.SetEnvironmentVariable("APS__ClientId", "bla");
25+
Environment.SetEnvironmentVariable("APS__ClientSecret", "blabla");
26+
var configuration = new ConfigurationBuilder()
27+
.AddEnvironmentVariables()
28+
.Build();
29+
30+
var services = new ServiceCollection();
31+
services.AddForgeService(configuration);
32+
var serviceProvider = services.BuildServiceProvider();
33+
34+
var config = serviceProvider.GetRequiredService<IOptions<ForgeConfiguration>>();
35+
Assert.Equal("bla", config.Value.ClientId);
36+
Assert.Equal("blabla", config.Value.ClientSecret);
37+
}
38+
39+
/// <summary>
40+
/// Tests using APS_CLIENT_ID and APS_CLIENT_SECRET environment variables
41+
/// </summary>
42+
43+
[Fact]
44+
public void TestAPSConfigFromEnvironmentVariables_UnderscoreFormat()
45+
{
46+
Environment.SetEnvironmentVariable("APS_CLIENT_ID", "bla");
47+
Environment.SetEnvironmentVariable("APS_CLIENT_SECRET", "blabla");
48+
var configuration = new ConfigurationBuilder()
49+
.AddAPSAlternativeEnvironmentVariables()
50+
.Build();
51+
var services = new ServiceCollection();
52+
services.AddForgeService(configuration);
53+
var serviceProvider = services.BuildServiceProvider();
54+
var config = serviceProvider.GetRequiredService<IOptions<ForgeConfiguration>>();
55+
Assert.Equal("bla", config.Value.ClientId);
56+
Assert.Equal("blabla", config.Value.ClientSecret);
57+
58+
}
59+
/// <summary>
60+
/// Tests loading APS configuration values from JSON with ClientId and ClientSecret
61+
/// </summary>
62+
63+
[Fact]
64+
public void TestAPSConfigFromJson()
65+
{
66+
var json = @"
67+
{
68+
""APS"" : {
69+
""ClientId"" : ""bla"",
70+
""ClientSecret"" : ""blabla""
71+
}
72+
}";
73+
var configuration = new ConfigurationBuilder()
74+
.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json)))
75+
.Build();
76+
77+
var services = new ServiceCollection();
78+
services.AddForgeService(configuration);
79+
var serviceProvider = services.BuildServiceProvider();
80+
81+
var config = serviceProvider.GetRequiredService<IOptions<ForgeConfiguration>>();
82+
Assert.Equal("bla", config.Value.ClientId);
83+
Assert.Equal("blabla", config.Value.ClientSecret);
84+
}
85+
86+
/// <summary>
87+
/// Tests loading APS configuration values from JSON with additional agent configurations
88+
/// </summary>
89+
90+
[Fact]
91+
public void TestAPSConfigFromJsonWithAgents()
92+
{
93+
var json = @"
94+
{
95+
""APS"" : {
96+
""ClientId"" : ""bla"",
97+
""ClientSecret"" : ""blabla"",
98+
""Agents"" : {
99+
""user1"" : {
100+
""ClientId"" : ""user1-bla"",
101+
""ClientSecret"" : ""user1-blabla""
102+
}
103+
}
104+
}
105+
}";
106+
var configuration = new ConfigurationBuilder()
107+
.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json)))
108+
.Build();
109+
110+
var services = new ServiceCollection();
111+
services.AddForgeService(configuration);
112+
var serviceProvider = services.BuildServiceProvider();
113+
114+
var config = serviceProvider.GetRequiredService<IOptions<ForgeConfiguration>>();
115+
Assert.Equal("bla", config.Value.ClientId);
116+
Assert.Equal("blabla", config.Value.ClientSecret);
117+
Assert.Equal("user1-bla", config.Value.Agents["user1"].ClientId);
118+
Assert.Equal("user1-blabla", config.Value.Agents["user1"].ClientSecret);
119+
}
120+
121+
/// <summary>
122+
/// Tests APS configuration for user agent "user1" and checks proper handling of authentication and request headers.
123+
/// </summary>
124+
[Fact]
125+
public async Task TestAPSUserAgent()
126+
{
127+
var json = @"
128+
{
129+
""APS"" : {
130+
""ClientId"" : ""bla"",
131+
""ClientSecret"" : ""blabla"",
132+
""Agents"" : {
133+
""user1"" : {
134+
""ClientId"" : ""user1-bla"",
135+
""ClientSecret"" : ""user1-blabla""
136+
}
137+
}
138+
}
139+
}";
140+
var configuration = new ConfigurationBuilder()
141+
.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json)))
142+
.Build();
143+
144+
var sink = new Mock<HttpMessageHandler>(MockBehavior.Strict);
145+
var services = new ServiceCollection();
146+
services.AddForgeService("user1", configuration).ConfigurePrimaryHttpMessageHandler(() => sink.Object);
147+
var serviceProvider = services.BuildServiceProvider();
148+
var config = serviceProvider.GetRequiredService<IOptions<ForgeConfiguration>>().Value;
149+
var req = new HttpRequestMessage();
150+
req.RequestUri = new Uri("http://example.com");
151+
req.Options.Set(ForgeConfiguration.ScopeKey, "somescope");
152+
153+
string user = null;
154+
sink.Protected().As<HttpMessageInvoker>().Setup(o => o.SendAsync(It.Is<HttpRequestMessage>(r => r.RequestUri == config.AuthenticationAddress), It.IsAny<CancellationToken>()))
155+
.ReturnsAsync(new HttpResponseMessage()
156+
{
157+
Content = new StringContent(JsonConvert.SerializeObject(new Dictionary<string, string> { { "token_type", "Bearer" }, { "access_token", "blablabla" }, { "expires_in", "3" } })),
158+
StatusCode = System.Net.HttpStatusCode.OK
159+
});
160+
sink.Protected().As<HttpMessageInvoker>().Setup(o => o.SendAsync(It.Is<HttpRequestMessage>(r => r.RequestUri == req.RequestUri), It.IsAny<CancellationToken>()))
161+
.Callback<HttpRequestMessage, CancellationToken>((r, ct) =>
162+
{
163+
r.Options.TryGetValue(ForgeConfiguration.AgentKey, out user);
164+
})
165+
.ReturnsAsync(new HttpResponseMessage()
166+
{
167+
StatusCode = System.Net.HttpStatusCode.OK
168+
});
169+
170+
171+
var clientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
172+
var client = clientFactory.CreateClient("user1");
173+
var resp = await client.SendAsync(req, CancellationToken.None);
174+
175+
sink.Protected().As<HttpMessageInvoker>().Verify(o => o.SendAsync(It.Is<HttpRequestMessage>(r => r.RequestUri == config.AuthenticationAddress), It.IsAny<CancellationToken>()), Times.Once());
176+
sink.Protected().As<HttpMessageInvoker>().Verify(o => o.SendAsync(It.Is<HttpRequestMessage>(r => r.RequestUri == req.RequestUri), It.IsAny<CancellationToken>()), Times.Once());
177+
Assert.Equal("user1", user);
178+
}
179+
}
180+
}

tests/Autodesk.Forge.Core.Test/TestForgeConfiguration.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,23 @@ public void TestValuesFromLegacyEnvironment()
5151
Assert.Equal("blabla", config.Value.ClientSecret);
5252
}
5353

54+
[Fact]
55+
public void TestValuesFromAPSEnvironment()
56+
{
57+
Environment.SetEnvironmentVariable("APS_CLIENT_ID", "bla");
58+
Environment.SetEnvironmentVariable("APS_CLIENT_SECRET", "blabla");
59+
var configuration = new ConfigurationBuilder()
60+
.AddAPSAlternativeEnvironmentVariables()
61+
.Build();
62+
var services = new ServiceCollection();
63+
services.AddForgeService(configuration);
64+
var serviceProvider = services.BuildServiceProvider();
65+
var config = serviceProvider.GetRequiredService<IOptions<ForgeConfiguration>>();
66+
Assert.Equal("bla", config.Value.ClientId);
67+
Assert.Equal("blabla",config.Value.ClientSecret);
68+
69+
}
70+
5471
[Fact]
5572
public void TestValuesFromJson()
5673
{

0 commit comments

Comments
 (0)