NikiforovAll / keycloak-authorization-services-dotnet

Authentication and Authorization with Keycloak and ASP.NET Core 🔐
https://nikiforovall.github.io/keycloak-authorization-services-dotnet/
MIT License
480 stars 113 forks source link

Attributes field no longer dictionary but object #108

Closed HOTAPIYC closed 5 months ago

HOTAPIYC commented 5 months ago

Hi,

many of the component schemas of the Keycloak OpenAPI spec contain a field named "attributes", which contain a dictionary that contains arrays of strings, specified like so:

"UserRepresentation": {
    "attributes": {
        "type": "object",
        "additionalProperties": {
            "type": "array",
            "items": {
            "type": "string"
        }
    }
}

To my understanding this translates to a Dictionary<string, string[]> or similar, which was also the case in the previous major version of this library: User

Now I really appreciate the introduction of the generated client, featuring all possible endpoints, for which I previously had to manually extend this library and which now works out-of-the-box like a charm. These attributes however are now of some different type: UserRepresentation.

As far as I understand this because of how Kiota handles the snippet above. Is there a way to restore the previous serialization? Or am I missing something else? Thanks!

NikiforovAll commented 5 months ago

Thank you for the feedback. It is the way the model is described in OpenAPI Schema so there is no way to override it since the client is automatically generated. I think you can still use UserRepresentation_attributes since it contains field AdditionalData of type Dictionary<string, object>. The only thing left is to cast object to string[] and it should work I believe.

HOTAPIYC commented 5 months ago

Thanks for the quick response! To add some context: this is for a web api that wraps the keycloak admin api which basically returns or accepts a subset of the same entities. The server stub for this web api is generated using the openapi csharp generator, reading the same openapi component schemas from the keycloak spec. As it turned out, the two generators do not return the same results.

I figured out a way to utilize Automapper though, that I am using within my project anyways. I added a custom converter for reading:

public class AdditionalDataConverter<T> : ITypeConverter<T, Dictionary<string, List<string>>>
    where T : IAdditionalDataHolder
{
    public Dictionary<string, List<string>> Convert(
        T source,
        Dictionary<string, List<string>> destination,
        ResolutionContext context)
    {
        return source?.AdditionalData?.ToDictionary(
            x => x.Key,
            x =>
            {
                var values = ((UntypedArray) x.Value).GetValue();
                return values.Select(x => ((UntypedString) x).GetValue()).ToList();
            });
    }
}

And for writing:

public class AttributesConverter<T> : ITypeConverter<Dictionary<string, List<string>>, T>
    where T : IAdditionalDataHolder, new()
{
    public T Convert(
        Dictionary<string, List<string>> source,
        T destination,
        ResolutionContext context)
    {
        var attributes = new T();
        attributes.AdditionalData = source.ToDictionary(
            x => x.Key,
            x => (object) x.Value);

        return attributes;
    }
}

This is added to the profiles e.g. like so:

public class EntityProfiles : Profile
{
    public EntityProfiles()
    {       
        CreateMap<Dictionary<string, List<string>>, UserRepresentation_attributes>()
            .ConvertUsing<AttributesConverter<UserRepresentation_attributes>>();
        CreateMap<Dictionary<string, List<string>>, GroupRepresentation_attributes>()
            .ConvertUsing<AttributesConverter<GroupRepresentation_attributes>>();
        CreateMap<Dictionary<string, List<string>>, ClientRepresentation_attributes>()
            .ConvertUsing<AttributesConverter<ClientRepresentation_attributes>>();
    }
}

I would have preferred doing it without, but I guess this will do the job for now. Thanks for your effort anyways!

NikiforovAll commented 5 months ago

Thank you for the added context, I think it might be useful for others. 🙌

NikiforovAll commented 5 months ago

I think you can go even further and scan for IAdditionalDataHolder and register converters programmatically based on assembly scanning results to avoid this situation in the future if needed

@HOTAPIYC