Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jwt token support, consolidate to use object type url, upgrade to dotnet 8 #17

Merged
merged 2 commits into from
May 13, 2024
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
12 changes: 8 additions & 4 deletions .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ ENTERPRISE_SEARCH_BASE_URL=
ENTERPRISE_SEARCH_PRIVATE_API_KEY=
SDG_OBJECTEN_BASE_URL=
SDG_OBJECTEN_TOKEN=
SDG_OBJECTEN_CLIENT_ID=
SDG_OBJECTEN_CLIENT_SECRET=
SDG_OBJECT_TYPE_URL=
MEDEWERKER_OBJECTEN_BASE_URL=
MEDEWERKER_OBJECTEN_TOKEN=
MEDEWERKER_OBJECTTYPES_BASE_URL=
MEDEWERKER_OBJECTTYPES_TOKEN=
MEDEWERKER_OBJECTEN_CLIENT_ID=
MEDEWERKER_OBJECTEN_CLIENT_SECRET=
MEDEWERKER_OBJECT_TYPE_URL=
VAC_OBJECTEN_BASE_URL=
VAC_OBJECTEN_TOKEN=
VAC_OBJECTTYPES_BASE_URL=
VAC_OBJECTTYPES_TOKEN=
VAC_OBJECTEN_CLIENT_ID=
VAC_OBJECTEN_CLIENT_SECRET=
VAC_OBJECT_TYPE_URL=
ELASTIC_USERNAME=
ELASTIC_PASSWORD=
ELASTIC_BASE_URL=
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -396,5 +396,5 @@ FodyWeavers.xsd

# JetBrains Rider
*.sln.iml
/.env.local
/.env*.local
/src/Kiss.Elastic.Sync/Properties/launchSettings.json
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ Examples of how to schedule a cron job in Kubernetes with these arguments [can b
### Variables for the different sources
| Variable | Description |
| --- | --- |
| SDG_BASE_URL | The base url for the SDG API to retrieve products |
| SDG_API_KEY | The API key to connect to the SDG API |
| MEDEWERKER_OBJECTEN_BASE_URL | The base url for the Objects API to retrieve mededewerkers (smoelenboek) |
| MEDEWERKER_OBJECTEN_TOKEN | The token to connect to the Objects API to retrieve medewerkers (smoelenboek) |
| MEDEWERKER_OBJECTTYPES_BASE_URL | The base url for the Object Types API to retrieve the medewerker object type (smoelenboek) |
| MEDEWERKER_OBJECTTYPES_TOKEN | The token to connect to the Object Types API to retrieve the medewerker object type (smoelenboek) |
| SDG_OBJECTEN_BASE_URL | The base url for the Objects API to retrieve Producten |
| SDG_OBJECT_TYPE_URL | The full url of the object type for Producten |
| SDG_OBJECTEN_TOKEN | The token to connect to the Objects API to retrieve Producten |
| MEDEWERKER_OBJECTEN_BASE_URL | The base url for the Objects API to retrieve mededewerkers (smoelenboek), or the PodiumD Adapter if applicable |
| MEDEWERKER_OBJECT_TYPE_URL | The full url of the object type for medewerkers (smoelenboek) |
| MEDEWERKER_OBJECTEN_TOKEN | The token to connect to the Objects API to retrieve medewerkers (smoelenboek). Use this if you are NOT using the PodiumD Adapter |
| MEDEWERKER_OBJECTEN_CLIENT_ID | The client id to generate a JWT to connect to the PodiumD Adapter to retrieve medewerkers (smoelenboek). This has to match a setting in the PodiumD Adapter |
| MEDEWERKER_OBJECTEN_CLIENT_SECRET | The client secret to generate a JWT to connect to the PodiumD Adapter to retrieve medewerkers (smoelenboek). This has to match a setting in the PodiumD Adapter |
| VAC_OBJECTEN_BASE_URL | The base url for the Objects API to retrieve VACs |
| VAC_OBJECT_TYPE_URL | The full url of the object type for VACs |
| VAC_OBJECTEN_TOKEN | The token to connect to the Objects API to retrieve VACs |
| VAC_OBJECTTYPES_BASE_URL | The base url for the Object Types API to retrieve the VAC object type |
| VAC_OBJECTTYPES_TOKEN | The token to connect to the Object Types API to retrieve the VAC object type |

18 changes: 5 additions & 13 deletions src/Kiss.Elastic.Sync/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
USER app
WORKDIR /app
RUN addgroup --group kiss --gid 2000 \
&& adduser \
--uid 1000 \
--gid 2000 \
"kiss"
RUN chown kiss:kiss /app

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS publish
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["src/Kiss.Elastic.Sync/Kiss.Elastic.Sync.csproj", "src/Kiss.Elastic.Sync/"]
RUN dotnet restore "src/Kiss.Elastic.Sync/Kiss.Elastic.Sync.csproj"
COPY . .
WORKDIR "/src/src/Kiss.Elastic.Sync"
RUN dotnet build "Kiss.Elastic.Sync.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "Kiss.Elastic.Sync.csproj" -c Release -o /app/publish /p:UseAppHost=false
RUN dotnet publish "Kiss.Elastic.Sync.csproj" --no-restore -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
USER kiss:kiss
ENTRYPOINT ["dotnet", "Kiss.Elastic.Sync.dll"]
6 changes: 3 additions & 3 deletions src/Kiss.Elastic.Sync/ElasticBulkClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ public ElasticBulkClient(Uri baseUri, string username, string password)

public static ElasticBulkClient Create()
{
var elasticBaseUrl = Helpers.GetEnvironmentVariable("ELASTIC_BASE_URL");
var username = Helpers.GetEnvironmentVariable("ELASTIC_USERNAME");
var password = Helpers.GetEnvironmentVariable("ELASTIC_PASSWORD");
var elasticBaseUrl = Helpers.GetRequiredEnvironmentVariable("ELASTIC_BASE_URL");
var username = Helpers.GetRequiredEnvironmentVariable("ELASTIC_USERNAME");
var password = Helpers.GetRequiredEnvironmentVariable("ELASTIC_PASSWORD");

if (!Uri.TryCreate(elasticBaseUrl, UriKind.Absolute, out var elasticBaseUri))
{
Expand Down
6 changes: 3 additions & 3 deletions src/Kiss.Elastic.Sync/ElasticEnterpriseSearchClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ public ElasticEnterpriseSearchClient(Uri baseUri, string apiKey, string metaEngi

public static ElasticEnterpriseSearchClient Create()
{
var elasticBaseUrl = Helpers.GetEnvironmentVariable("ENTERPRISE_SEARCH_BASE_URL");
var elasticApiKey = Helpers.GetEnvironmentVariable("ENTERPRISE_SEARCH_PRIVATE_API_KEY");
var elasticEngine = Helpers.GetEnvironmentVariable("ENTERPRISE_SEARCH_ENGINE");
var elasticBaseUrl = Helpers.GetRequiredEnvironmentVariable("ENTERPRISE_SEARCH_BASE_URL");
var elasticApiKey = Helpers.GetRequiredEnvironmentVariable("ENTERPRISE_SEARCH_PRIVATE_API_KEY");
var elasticEngine = Helpers.GetRequiredEnvironmentVariable("ENTERPRISE_SEARCH_ENGINE");

if (!Uri.TryCreate(elasticBaseUrl, UriKind.Absolute, out var elasticBaseUri))
{
Expand Down
3 changes: 2 additions & 1 deletion src/Kiss.Elastic.Sync/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public static void CancelSafely(this CancellationTokenSource source)
}
}

public static string GetEnvironmentVariable(string name) => Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process) ?? throw new Exception("missing environment variable: " + name);
public static string GetRequiredEnvironmentVariable(string name) => GetOptionalEnvironmentVariable(name) ?? throw new Exception("missing environment variable: " + name);
public static string? GetOptionalEnvironmentVariable(string name) => Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process);

public static string EncodeCredential(string userName, string password)
{
Expand Down
5 changes: 3 additions & 2 deletions src/Kiss.Elastic.Sync/Kiss.Elastic.Sync.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
Expand All @@ -22,7 +22,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.2" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
</ItemGroup>

</Project>
65 changes: 0 additions & 65 deletions src/Kiss.Elastic.Sync/Objecten/ObjectTypesClient.cs

This file was deleted.

51 changes: 48 additions & 3 deletions src/Kiss.Elastic.Sync/Objecten/ObjectenClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
using System.Runtime.CompilerServices;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Web;
using Microsoft.IdentityModel.Tokens;

namespace Kiss.Elastic.Sync.Sources
{
Expand All @@ -10,13 +15,26 @@ public sealed class ObjectenClient
{
private readonly HttpClient _httpClient;

public ObjectenClient(Uri objectenBaseUri, string objectenToken)
public ObjectenClient(Uri objectenBaseUri, string? objectenToken, string? objectenClientId, string? objectenClientSecret)
{
_httpClient = new HttpClient
{
BaseAddress = objectenBaseUri
};
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Token", objectenToken);

if (!string.IsNullOrWhiteSpace(objectenClientId) && !string.IsNullOrWhiteSpace(objectenClientSecret))
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", GetToken(objectenClientId, objectenClientSecret));
return;
}

if (!string.IsNullOrWhiteSpace(objectenToken))
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", objectenToken);
return;
}

throw new Exception("No token or client id/secret is configured for the ObjectenClient");
}

public IAsyncEnumerable<OverigObject> GetObjecten(string type, CancellationToken token)
Expand All @@ -25,6 +43,33 @@ public IAsyncEnumerable<OverigObject> GetObjecten(string type, CancellationToken
return GetObjectenInternal(url, token);
}

private static string GetToken(string id, string secret)
{
var now = DateTimeOffset.UtcNow;
// one minute leeway to account for clock differences between machines
var issuedAt = now.AddMinutes(-1);

var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = id,
IssuedAt = issuedAt.DateTime,
NotBefore = issuedAt.DateTime,
Claims = new Dictionary<string, object>
{
{ "client_id", id },
{ "user_id", "KISS Elastic Sync"},
{ "user_representation", "elastic-sync" }
},
Subject = new ClaimsIdentity(),
Expires = now.AddHours(1).DateTime,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}

private async IAsyncEnumerable<OverigObject> GetObjectenInternal(string url, [EnumeratorCancellation] CancellationToken token)
{
string? next = null;
Expand Down
31 changes: 13 additions & 18 deletions src/Kiss.Elastic.Sync/Sources/ObjectenMedewerkerClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Runtime.CompilerServices;
using System.Text.Json;
using Kiss.Elastic.Sync.Objecten;

namespace Kiss.Elastic.Sync.Sources
{
Expand All @@ -9,7 +8,7 @@ internal sealed class ObjectenMedewerkerClient : IKissSourceClient
private static readonly string[] s_nameProps = new[] { "voornaam", "voorvoegselAchternaam", "achternaam" };
private static readonly string[] s_metaProps = new[] { "function", "department", "skills" };
private readonly ObjectenClient _objectenClient;
private readonly ObjectTypesClient _objectTypesClient;
private readonly string _objecttypeUrl;

public string Source => "Smoelenboek";

Expand All @@ -27,30 +26,27 @@ internal sealed class ObjectenMedewerkerClient : IKissSourceClient
"telefoonnummers.telefoonnummer"
};

public ObjectenMedewerkerClient(ObjectenClient objectenClient, ObjectTypesClient objectTypesClient)
public ObjectenMedewerkerClient(ObjectenClient objectenClient, string objecttypeUrl)
{
_objectenClient = objectenClient;
_objectTypesClient = objectTypesClient;
_objecttypeUrl = objecttypeUrl;
}

public async IAsyncEnumerable<KissEnvelope> Get([EnumeratorCancellation] CancellationToken token)
{
await foreach (var type in _objectTypesClient.GetObjectTypeUrls("Medewerker", token))
await foreach (var item in _objectenClient.GetObjecten(_objecttypeUrl, token))
{
await foreach (var item in _objectenClient.GetObjecten(type, token))
{
var data = item.Data;
if (!data.TryGetProperty("identificatie", out var idProp) || idProp.ValueKind != JsonValueKind.String)
continue;
var data = item.Data;
if (!data.TryGetProperty("identificatie", out var idProp) || idProp.ValueKind != JsonValueKind.String)
continue;

var title = string.Join(' ', GetStringValues(data, s_nameProps));

var objectMeta = data.TryGetProperty("functie", out var functieProp) && functieProp.ValueKind == JsonValueKind.String
? functieProp.GetString()
: null;
var title = string.Join(' ', GetStringValues(data, s_nameProps));

yield return new KissEnvelope(data, title, objectMeta, $"smoelenboek_{idProp.GetString()}");
}
var objectMeta = data.TryGetProperty("functie", out var functieProp) && functieProp.ValueKind == JsonValueKind.String
? functieProp.GetString()
: null;

yield return new KissEnvelope(data, title, objectMeta, $"smoelenboek_{idProp.GetString()}");
}
}

Expand All @@ -71,7 +67,6 @@ private static IEnumerable<string> GetStringValues(JsonElement element, string[]
public void Dispose()
{
_objectenClient.Dispose();
_objectTypesClient.Dispose();
}
}
}
Loading
Loading