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
10 changes: 10 additions & 0 deletions FoxIDs.Samples.sln
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Actions", "Actions", "{D890
.github\workflows\ci_main_docker.yaml = .github\workflows\ci_main_docker.yaml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FoxIDs Control API", "FoxIDs Control API", "{341BB81B-1FCC-4BB1-8327-52E1E6C2DE58}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FoxIDs.ControlApiSample", "src\FoxIDs.ControlApiSample\FoxIDs.ControlApiSample.csproj", "{99326EFD-A673-496C-A009-483413A4C22B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -150,6 +154,10 @@ Global
{8E5FBE66-AA33-4A05-B4C2-542A1A3D2887}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E5FBE66-AA33-4A05-B4C2-542A1A3D2887}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E5FBE66-AA33-4A05-B4C2-542A1A3D2887}.Release|Any CPU.Build.0 = Release|Any CPU
{99326EFD-A673-496C-A009-483413A4C22B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{99326EFD-A673-496C-A009-483413A4C22B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{99326EFD-A673-496C-A009-483413A4C22B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{99326EFD-A673-496C-A009-483413A4C22B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -179,6 +187,8 @@ Global
{CA8922BB-B413-4DD6-AEE1-047310D43938} = {3BD3C68B-BDA7-4114-BC2A-E56D1D317F77}
{8E5FBE66-AA33-4A05-B4C2-542A1A3D2887} = {CA8922BB-B413-4DD6-AEE1-047310D43938}
{D890B671-7405-4FB8-8E86-04997FCDF293} = {1C415060-9F5A-4CDE-B14E-1EF3B51C9039}
{341BB81B-1FCC-4BB1-8327-52E1E6C2DE58} = {3BD3C68B-BDA7-4114-BC2A-E56D1D317F77}
{99326EFD-A673-496C-A009-483413A4C22B} = {341BB81B-1FCC-4BB1-8327-52E1E6C2DE58}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E093D1EA-7966-4F7F-BA00-DC599050299F}
Expand Down
66 changes: 66 additions & 0 deletions src/FoxIDs.ControlApiSample/Extensions/HttpClientExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using ITfoxtec.Identity;
using Microsoft.AspNetCore.WebUtilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Mime;
using System.Text;
using System.Threading.Tasks;

namespace FoxIDs.ControlApiSample
{
public static class HttpClientExtensions
{
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore
};

public static Task<HttpResponseMessage> GetJsonAsync(this HttpClient client, string requestUri, params object[] data)
{
var nameValueCollection = new Dictionary<string, string>();
if (data != null && data.Count() > 0)
{

nameValueCollection = data[0].ToDictionary();
if (data.Count() > 1)
{
foreach (var item in data.Skip(1).Where(d => d != null))
{
nameValueCollection = nameValueCollection.AddToDictionary(item);
}
}
}
return client.GetAsync(QueryHelpers.AddQueryString(requestUri, nameValueCollection));
}

public static Task<HttpResponseMessage> PostJsonAsync(this HttpClient client, string requestUri, object data)
{
var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
request.Content = new StringContent(JsonConvert.SerializeObject(data, Settings), Encoding.UTF8, MediaTypeNames.Application.Json);
return client.SendAsync(request);
}

public static Task<HttpResponseMessage> UpdateJsonAsync(this HttpClient client, string requestUri, object data)
{
var request = new HttpRequestMessage(HttpMethod.Put, requestUri);
request.Content = new StringContent(JsonConvert.SerializeObject(data, Settings), Encoding.UTF8, MediaTypeNames.Application.Json);
return client.SendAsync(request);
}
public static Task<HttpResponseMessage> PatchJsonAsync(this HttpClient client, string requestUri, object data)
{
var request = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri);
request.Content = new StringContent(JsonConvert.SerializeObject(data, Settings), Encoding.UTF8, MediaTypeNames.Application.Json);
return client.SendAsync(request);
}

public static Task<HttpResponseMessage> DeleteJsonAsync(this HttpClient client, string requestUri, object data)
{
var nameValueCollection = data.ToDictionary();
return client.DeleteAsync(QueryHelpers.AddQueryString(requestUri, nameValueCollection));
}
}
}
51 changes: 51 additions & 0 deletions src/FoxIDs.ControlApiSample/FoxIDs.ControlApiSample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<AssemblyName>FoxIDs.ControlApiSample</AssemblyName>
<StartupObject></StartupObject>
<RootNamespace>FoxIDs.ControlApiSample</RootNamespace>
<GenerateCode>False</GenerateCode>
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="ITfoxtec.Identity" Version="2.13.6" />
<PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="11.1.0" />
<PackageReference Include="NSwag.MSBuild" Version="14.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<Content Include="appsettings*json">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

<ItemGroup>
<None Update="AspNetCoreSamlSample-test-sign-cert.crt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="CN=AspNetCoreApi1Sample, O=test corp.cer">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="CN=NetCoreClientAssertionGrantConsoleSample, O=test corp.cer">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="CN=TokenExchangeAspnetcoreSamlSample, O=test corp.cer">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="identityserver-tempkey.jwk">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<Target Name="NSwag" BeforeTargets="PrepareForBuild" Condition="'$(GenerateCode)'=='True' ">
<Exec Command="$(NSwagExe_Net80) run ServiceAccess\nswag.json /variables:Configuration=$(Configuration)" />
</Target>

</Project>
75 changes: 75 additions & 0 deletions src/FoxIDs.ControlApiSample/Infrastructure/StartupConfigure.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using FoxIDs.ControlApiSample.Logic;
using FoxIDs.ControlApiSample.Models;
using FoxIDs.ControlApiSample.ServiceAccess;
using ITfoxtec.Identity;
using ITfoxtec.Identity.Discovery;
using ITfoxtec.Identity.Helpers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.IO;
using System.Net.Http;
using ITfoxtec.Identity.Util;

namespace FoxIDs.ControlApiSample.Infrastructure
{
public class StartupConfigure
{
private ServiceCollection services;

public IServiceProvider ConfigureServices()
{
services = new ServiceCollection();

AddConfiguration();
AddInfrastructure(services);
AddLogic(services);

var serviceProvider = services.BuildServiceProvider();
return serviceProvider;
}

private static void AddLogic(ServiceCollection services)
{
services.AddSingleton<AccessLogic>();

services.AddTransient<ApiSampleLogic>();
}

private static void AddInfrastructure(ServiceCollection services)
{
services.AddHttpClient();

services.AddTransient<TokenHelper>();
services.AddSingleton(serviceProvider =>
{
var settings = serviceProvider.GetService<ApiSampleSettings>();
var httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();

return new OidcDiscoveryHandler(httpClientFactory, UrlCombine.Combine(settings.Authority, IdentityConstants.OidcDiscovery.Path));
});

services.AddTransient(serviceProvider =>
{
var settings = serviceProvider.GetService<ApiSampleSettings>();
var httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
var accessLogic = serviceProvider.GetService<AccessLogic>();

return new FoxIDsApiClient(settings, httpClientFactory, accessLogic);
});
}

private void AddConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.Development.json", optional: true);

var configuration = builder.Build();
var seedSettings = new ApiSampleSettings();
configuration.Bind(nameof(ApiSampleSettings), seedSettings);
services.AddSingleton(seedSettings);
}
}
}
34 changes: 34 additions & 0 deletions src/FoxIDs.ControlApiSample/Logic/AccessLogic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using ITfoxtec.Identity.Helpers;
using FoxIDs.ControlApiSample.Models;
using System;
using System.Threading.Tasks;

namespace FoxIDs.ControlApiSample.Logic
{
public class AccessLogic
{
private readonly ApiSampleSettings settings;
private readonly TokenHelper tokenHelper;
private string accessTokenCache;
private long cacheExpiresAt;

public AccessLogic(ApiSampleSettings settings, TokenHelper tokenHelper)
{
this.settings = settings;
this.tokenHelper = tokenHelper;
}

public async Task<string> GetAccessTokenAsync()
{
if (cacheExpiresAt < DateTimeOffset.UtcNow.AddSeconds(-5).ToUnixTimeSeconds())
{
Console.WriteLine("\t\tAcquire sample seed client access token...");
(var accessToken, var expiresIn) = await tokenHelper.GetAccessTokenWithClientCredentialGrantAsync(settings.ClientId, settings.ClientSecret, settings.Scope);
accessTokenCache = accessToken;
cacheExpiresAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + expiresIn.Value;
Console.WriteLine($"\t\tAccess token: {accessToken.Substring(0, 40)}...");
}
return accessTokenCache;
}
}
}
43 changes: 43 additions & 0 deletions src/FoxIDs.ControlApiSample/Logic/ApiSampleLogic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using FoxIDs.ControlApiSample.Models;
using FoxIDs.ControlApiSample.ServiceAccess;
using FoxIDs.ControlApiSample.ServiceAccess.Contracts;
using System;
using System.Threading.Tasks;

namespace FoxIDs.ControlApiSample.Logic
{
public class ApiSampleLogic
{
private readonly ApiSampleSettings settings;
private readonly FoxIDsApiClient foxIDsApiClient;

public ApiSampleLogic(ApiSampleSettings settings, FoxIDsApiClient foxIDsApiClient)
{
this.settings = settings;
this.foxIDsApiClient = foxIDsApiClient;
}

public async Task ChangeUsersPasswordAsync()
{
Console.WriteLine($"Change a user's password in [tenant: '{settings.Tenant}', track (environment): '{settings.Track}']");
Console.WriteLine("Add the user's email, current and new password");
Console.Write("Email: ");
var email = Console.ReadLine();
Console.Write("Current password: ");
var currentPassword = Console.ReadLine();
Console.Write("New password: ");
var newPassword = Console.ReadLine();
Console.WriteLine(string.Empty);

var user = await foxIDsApiClient.PutUserChangePasswordAsync(new UserChangePasswordRequest
{
Email = email,
CurrentPassword = currentPassword,
NewPassword = newPassword
});

Console.WriteLine(string.Empty);
Console.WriteLine($"The user's password has been changed");
}
}
}
55 changes: 55 additions & 0 deletions src/FoxIDs.ControlApiSample/Models/ApiSampleSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.ComponentModel.DataAnnotations;
using ITfoxtec.Identity.Util;

namespace FoxIDs.ControlApiSample.Models
{
public class ApiSampleSettings
{
/// <summary>
/// API Sample client id.
/// </summary>
[Required]
public string ClientId { get; set; }
/// <summary>
/// Sample seed tool client secret.
/// </summary>
[Required]
public string ClientSecret { get; set; }

/// <summary>
/// Space delimited list of scopes.
/// </summary>
[Required]
public string Scope { get; set; }

/// <summary>
/// FoxIDs endpoint.
/// </summary>
[Required]
public string FoxIDsEndpoint { get; set; }
/// <summary>
/// Set to false if you have configured a custom domain.
/// </summary>
public bool IncludeTenantInUrl { get; set; }
/// <summary>
/// Sample seed tool tenant.
/// </summary>
[Required]
public string Tenant { get; set; }
/// <summary>
/// Sample seed tool track.
/// </summary>
[Required]
public string Track { get; set; }
/// <summary>
/// FoxIDs tenant/track/application authority.
/// </summary>
public string Authority => UrlCombine.Combine(FoxIDsEndpoint, Tenant, "master", ClientId);

/// <summary>
/// FoxIDs API endpoint.
/// </summary>
[Required]
public string FoxIDsConsolApiEndpoint { get; set; }
}
}
Loading