Skip to content

Commit 2785d3a

Browse files
authored
Merge pull request #110 from ITfoxtec/development
Development
2 parents ae9a11e + 4bedfc6 commit 2785d3a

File tree

16 files changed

+23150
-3
lines changed

16 files changed

+23150
-3
lines changed

FoxIDs.Samples.sln

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Actions", "Actions", "{D890
6464
.github\workflows\ci_main_docker.yaml = .github\workflows\ci_main_docker.yaml
6565
EndProjectSection
6666
EndProject
67+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FoxIDs Control API", "FoxIDs Control API", "{341BB81B-1FCC-4BB1-8327-52E1E6C2DE58}"
68+
EndProject
69+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FoxIDs.ControlApiSample", "src\FoxIDs.ControlApiSample\FoxIDs.ControlApiSample.csproj", "{99326EFD-A673-496C-A009-483413A4C22B}"
70+
EndProject
6771
Global
6872
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6973
Debug|Any CPU = Debug|Any CPU
@@ -150,6 +154,10 @@ Global
150154
{8E5FBE66-AA33-4A05-B4C2-542A1A3D2887}.Debug|Any CPU.Build.0 = Debug|Any CPU
151155
{8E5FBE66-AA33-4A05-B4C2-542A1A3D2887}.Release|Any CPU.ActiveCfg = Release|Any CPU
152156
{8E5FBE66-AA33-4A05-B4C2-542A1A3D2887}.Release|Any CPU.Build.0 = Release|Any CPU
157+
{99326EFD-A673-496C-A009-483413A4C22B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
158+
{99326EFD-A673-496C-A009-483413A4C22B}.Debug|Any CPU.Build.0 = Debug|Any CPU
159+
{99326EFD-A673-496C-A009-483413A4C22B}.Release|Any CPU.ActiveCfg = Release|Any CPU
160+
{99326EFD-A673-496C-A009-483413A4C22B}.Release|Any CPU.Build.0 = Release|Any CPU
153161
EndGlobalSection
154162
GlobalSection(SolutionProperties) = preSolution
155163
HideSolutionNode = FALSE
@@ -179,6 +187,8 @@ Global
179187
{CA8922BB-B413-4DD6-AEE1-047310D43938} = {3BD3C68B-BDA7-4114-BC2A-E56D1D317F77}
180188
{8E5FBE66-AA33-4A05-B4C2-542A1A3D2887} = {CA8922BB-B413-4DD6-AEE1-047310D43938}
181189
{D890B671-7405-4FB8-8E86-04997FCDF293} = {1C415060-9F5A-4CDE-B14E-1EF3B51C9039}
190+
{341BB81B-1FCC-4BB1-8327-52E1E6C2DE58} = {3BD3C68B-BDA7-4114-BC2A-E56D1D317F77}
191+
{99326EFD-A673-496C-A009-483413A4C22B} = {341BB81B-1FCC-4BB1-8327-52E1E6C2DE58}
182192
EndGlobalSection
183193
GlobalSection(ExtensibilityGlobals) = postSolution
184194
SolutionGuid = {E093D1EA-7966-4F7F-BA00-DC599050299F}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using ITfoxtec.Identity;
2+
using Microsoft.AspNetCore.WebUtilities;
3+
using Newtonsoft.Json;
4+
using Newtonsoft.Json.Serialization;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Net.Http;
8+
using System.Net.Mime;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
12+
namespace FoxIDs.ControlApiSample
13+
{
14+
public static class HttpClientExtensions
15+
{
16+
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
17+
{
18+
ContractResolver = new CamelCasePropertyNamesContractResolver(),
19+
NullValueHandling = NullValueHandling.Ignore
20+
};
21+
22+
public static Task<HttpResponseMessage> GetJsonAsync(this HttpClient client, string requestUri, params object[] data)
23+
{
24+
var nameValueCollection = new Dictionary<string, string>();
25+
if (data != null && data.Count() > 0)
26+
{
27+
28+
nameValueCollection = data[0].ToDictionary();
29+
if (data.Count() > 1)
30+
{
31+
foreach (var item in data.Skip(1).Where(d => d != null))
32+
{
33+
nameValueCollection = nameValueCollection.AddToDictionary(item);
34+
}
35+
}
36+
}
37+
return client.GetAsync(QueryHelpers.AddQueryString(requestUri, nameValueCollection));
38+
}
39+
40+
public static Task<HttpResponseMessage> PostJsonAsync(this HttpClient client, string requestUri, object data)
41+
{
42+
var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
43+
request.Content = new StringContent(JsonConvert.SerializeObject(data, Settings), Encoding.UTF8, MediaTypeNames.Application.Json);
44+
return client.SendAsync(request);
45+
}
46+
47+
public static Task<HttpResponseMessage> UpdateJsonAsync(this HttpClient client, string requestUri, object data)
48+
{
49+
var request = new HttpRequestMessage(HttpMethod.Put, requestUri);
50+
request.Content = new StringContent(JsonConvert.SerializeObject(data, Settings), Encoding.UTF8, MediaTypeNames.Application.Json);
51+
return client.SendAsync(request);
52+
}
53+
public static Task<HttpResponseMessage> PatchJsonAsync(this HttpClient client, string requestUri, object data)
54+
{
55+
var request = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri);
56+
request.Content = new StringContent(JsonConvert.SerializeObject(data, Settings), Encoding.UTF8, MediaTypeNames.Application.Json);
57+
return client.SendAsync(request);
58+
}
59+
60+
public static Task<HttpResponseMessage> DeleteJsonAsync(this HttpClient client, string requestUri, object data)
61+
{
62+
var nameValueCollection = data.ToDictionary();
63+
return client.DeleteAsync(QueryHelpers.AddQueryString(requestUri, nameValueCollection));
64+
}
65+
}
66+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<AssemblyName>FoxIDs.ControlApiSample</AssemblyName>
7+
<StartupObject></StartupObject>
8+
<RootNamespace>FoxIDs.ControlApiSample</RootNamespace>
9+
<GenerateCode>False</GenerateCode>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
14+
<PackageReference Include="ITfoxtec.Identity" Version="2.13.6" />
15+
<PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="11.1.0" />
16+
<PackageReference Include="NSwag.MSBuild" Version="14.2.0">
17+
<PrivateAssets>all</PrivateAssets>
18+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
19+
</PackageReference>
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<Content Include="appsettings*json">
24+
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
25+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
26+
</Content>
27+
</ItemGroup>
28+
29+
<ItemGroup>
30+
<None Update="AspNetCoreSamlSample-test-sign-cert.crt">
31+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
32+
</None>
33+
<None Update="CN=AspNetCoreApi1Sample, O=test corp.cer">
34+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
35+
</None>
36+
<None Update="CN=NetCoreClientAssertionGrantConsoleSample, O=test corp.cer">
37+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
38+
</None>
39+
<None Update="CN=TokenExchangeAspnetcoreSamlSample, O=test corp.cer">
40+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
41+
</None>
42+
<None Update="identityserver-tempkey.jwk">
43+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
44+
</None>
45+
</ItemGroup>
46+
47+
<Target Name="NSwag" BeforeTargets="PrepareForBuild" Condition="'$(GenerateCode)'=='True' ">
48+
<Exec Command="$(NSwagExe_Net80) run ServiceAccess\nswag.json /variables:Configuration=$(Configuration)" />
49+
</Target>
50+
51+
</Project>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using FoxIDs.ControlApiSample.Logic;
2+
using FoxIDs.ControlApiSample.Models;
3+
using FoxIDs.ControlApiSample.ServiceAccess;
4+
using ITfoxtec.Identity;
5+
using ITfoxtec.Identity.Discovery;
6+
using ITfoxtec.Identity.Helpers;
7+
using Microsoft.Extensions.Configuration;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using System;
10+
using System.IO;
11+
using System.Net.Http;
12+
using ITfoxtec.Identity.Util;
13+
14+
namespace FoxIDs.ControlApiSample.Infrastructure
15+
{
16+
public class StartupConfigure
17+
{
18+
private ServiceCollection services;
19+
20+
public IServiceProvider ConfigureServices()
21+
{
22+
services = new ServiceCollection();
23+
24+
AddConfiguration();
25+
AddInfrastructure(services);
26+
AddLogic(services);
27+
28+
var serviceProvider = services.BuildServiceProvider();
29+
return serviceProvider;
30+
}
31+
32+
private static void AddLogic(ServiceCollection services)
33+
{
34+
services.AddSingleton<AccessLogic>();
35+
36+
services.AddTransient<ApiSampleLogic>();
37+
}
38+
39+
private static void AddInfrastructure(ServiceCollection services)
40+
{
41+
services.AddHttpClient();
42+
43+
services.AddTransient<TokenHelper>();
44+
services.AddSingleton(serviceProvider =>
45+
{
46+
var settings = serviceProvider.GetService<ApiSampleSettings>();
47+
var httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
48+
49+
return new OidcDiscoveryHandler(httpClientFactory, UrlCombine.Combine(settings.Authority, IdentityConstants.OidcDiscovery.Path));
50+
});
51+
52+
services.AddTransient(serviceProvider =>
53+
{
54+
var settings = serviceProvider.GetService<ApiSampleSettings>();
55+
var httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
56+
var accessLogic = serviceProvider.GetService<AccessLogic>();
57+
58+
return new FoxIDsApiClient(settings, httpClientFactory, accessLogic);
59+
});
60+
}
61+
62+
private void AddConfiguration()
63+
{
64+
var builder = new ConfigurationBuilder()
65+
.SetBasePath(Directory.GetCurrentDirectory())
66+
.AddJsonFile("appsettings.json")
67+
.AddJsonFile("appsettings.Development.json", optional: true);
68+
69+
var configuration = builder.Build();
70+
var seedSettings = new ApiSampleSettings();
71+
configuration.Bind(nameof(ApiSampleSettings), seedSettings);
72+
services.AddSingleton(seedSettings);
73+
}
74+
}
75+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using ITfoxtec.Identity.Helpers;
2+
using FoxIDs.ControlApiSample.Models;
3+
using System;
4+
using System.Threading.Tasks;
5+
6+
namespace FoxIDs.ControlApiSample.Logic
7+
{
8+
public class AccessLogic
9+
{
10+
private readonly ApiSampleSettings settings;
11+
private readonly TokenHelper tokenHelper;
12+
private string accessTokenCache;
13+
private long cacheExpiresAt;
14+
15+
public AccessLogic(ApiSampleSettings settings, TokenHelper tokenHelper)
16+
{
17+
this.settings = settings;
18+
this.tokenHelper = tokenHelper;
19+
}
20+
21+
public async Task<string> GetAccessTokenAsync()
22+
{
23+
if (cacheExpiresAt < DateTimeOffset.UtcNow.AddSeconds(-5).ToUnixTimeSeconds())
24+
{
25+
Console.WriteLine("\t\tAcquire sample seed client access token...");
26+
(var accessToken, var expiresIn) = await tokenHelper.GetAccessTokenWithClientCredentialGrantAsync(settings.ClientId, settings.ClientSecret, settings.Scope);
27+
accessTokenCache = accessToken;
28+
cacheExpiresAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + expiresIn.Value;
29+
Console.WriteLine($"\t\tAccess token: {accessToken.Substring(0, 40)}...");
30+
}
31+
return accessTokenCache;
32+
}
33+
}
34+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using FoxIDs.ControlApiSample.Models;
2+
using FoxIDs.ControlApiSample.ServiceAccess;
3+
using FoxIDs.ControlApiSample.ServiceAccess.Contracts;
4+
using System;
5+
using System.Threading.Tasks;
6+
7+
namespace FoxIDs.ControlApiSample.Logic
8+
{
9+
public class ApiSampleLogic
10+
{
11+
private readonly ApiSampleSettings settings;
12+
private readonly FoxIDsApiClient foxIDsApiClient;
13+
14+
public ApiSampleLogic(ApiSampleSettings settings, FoxIDsApiClient foxIDsApiClient)
15+
{
16+
this.settings = settings;
17+
this.foxIDsApiClient = foxIDsApiClient;
18+
}
19+
20+
public async Task ChangeUsersPasswordAsync()
21+
{
22+
Console.WriteLine($"Change a user's password in [tenant: '{settings.Tenant}', track (environment): '{settings.Track}']");
23+
Console.WriteLine("Add the user's email, current and new password");
24+
Console.Write("Email: ");
25+
var email = Console.ReadLine();
26+
Console.Write("Current password: ");
27+
var currentPassword = Console.ReadLine();
28+
Console.Write("New password: ");
29+
var newPassword = Console.ReadLine();
30+
Console.WriteLine(string.Empty);
31+
32+
var user = await foxIDsApiClient.PutUserChangePasswordAsync(new UserChangePasswordRequest
33+
{
34+
Email = email,
35+
CurrentPassword = currentPassword,
36+
NewPassword = newPassword
37+
});
38+
39+
Console.WriteLine(string.Empty);
40+
Console.WriteLine($"The user's password has been changed");
41+
}
42+
}
43+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using ITfoxtec.Identity.Util;
3+
4+
namespace FoxIDs.ControlApiSample.Models
5+
{
6+
public class ApiSampleSettings
7+
{
8+
/// <summary>
9+
/// API Sample client id.
10+
/// </summary>
11+
[Required]
12+
public string ClientId { get; set; }
13+
/// <summary>
14+
/// Sample seed tool client secret.
15+
/// </summary>
16+
[Required]
17+
public string ClientSecret { get; set; }
18+
19+
/// <summary>
20+
/// Space delimited list of scopes.
21+
/// </summary>
22+
[Required]
23+
public string Scope { get; set; }
24+
25+
/// <summary>
26+
/// FoxIDs endpoint.
27+
/// </summary>
28+
[Required]
29+
public string FoxIDsEndpoint { get; set; }
30+
/// <summary>
31+
/// Set to false if you have configured a custom domain.
32+
/// </summary>
33+
public bool IncludeTenantInUrl { get; set; }
34+
/// <summary>
35+
/// Sample seed tool tenant.
36+
/// </summary>
37+
[Required]
38+
public string Tenant { get; set; }
39+
/// <summary>
40+
/// Sample seed tool track.
41+
/// </summary>
42+
[Required]
43+
public string Track { get; set; }
44+
/// <summary>
45+
/// FoxIDs tenant/track/application authority.
46+
/// </summary>
47+
public string Authority => UrlCombine.Combine(FoxIDsEndpoint, Tenant, "master", ClientId);
48+
49+
/// <summary>
50+
/// FoxIDs API endpoint.
51+
/// </summary>
52+
[Required]
53+
public string FoxIDsConsolApiEndpoint { get; set; }
54+
}
55+
}

0 commit comments

Comments
 (0)