okta / okta-sdk-dotnet

A .NET SDK for interacting with the Okta management API, enabling server-side code to manage Okta users, groups, applications, and more.
Other
160 stars 100 forks source link

AppAndInstanceConditionEvaluatorAppOrInstance ID property setter is private #718

Closed rcollette closed 1 month ago

rcollette commented 6 months ago

Describe the bug?

When creating an IdpDiscoveryPolicyRule and attempting to set the rule to apply to a specific application, I am unable to set the Id property in AppAndInstanceConditionEvaluatorAppOrInstance, but it is clearly a valid property that can be set when calling the API as per the documentation at https://developer.okta.com/docs/reference/api/policy/#application-and-app-instance-condition-object.

What is expected to happen?

AppAndInstanceConditionEvaluatorAppOrInstance.Id should be settable.

What is the actual behavior?

It is not settable.

Reproduction Steps?

This is pretty straight forward. Reproduction isn't necessary.

Additional Information?

No response

.NET Version

8.0.300

SDK Version

7.0.6

OS version

Darwin MacBook-Pro-3.local 23.4.0 Darwin Kernel Version 23.4.0: Fri Mar 15 00:10:42 PDT 2024; root:xnu-10063.101.17~1/RELEASE_ARM64_T6000 arm64

rcollette commented 6 months ago

Yet another workaround.

Set the ID property using reflection

    private static readonly PropertyInfo? s_idAccessor =
        typeof(AppAndInstanceConditionEvaluatorAppOrInstance).GetProperty(
            name: "Id");

    public static IdpDiscoveryPolicyRuleCondition CreateForUser(string mailAddress, string appId)
    {
        AppAndInstanceConditionEvaluatorAppOrInstance appInstance = new() { Type = AppAndInstanceType.APP };

        // set the app instance id to appId using reflection since the Id property has a private setter
        // See: https://github.com/okta/okta-sdk-dotnet/issues/718
        s_idAccessor!.SetValue(appInstance, appId);
        return new IdpDiscoveryPolicyRuleCondition
        {
            UserIdentifier = UserIdentifier.CreateForUser(mailAddress),
            App = new AppAndInstancePolicyRuleCondition { Include = [appInstance] }
        };
    }

Newtonsoft doesn't serialize properties with private setters by default, so I added yet another converter.

using System;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Okta.Sdk.Model;

namespace Precisely.Identity.Api.HttpClient.Okta.Schema;

public class AppAndInstanceConditionEvaluatorAppOrInstanceConverter : JsonConverter
{
    private static JsonSerializerSettings CloneSerializerSettings(JsonSerializer serializer)
    {
        return new JsonSerializerSettings
        {
            Culture = serializer.Culture,
            DateFormatHandling = serializer.DateFormatHandling,
            DateTimeZoneHandling = serializer.DateTimeZoneHandling,
            DefaultValueHandling = serializer.DefaultValueHandling,
            Formatting = serializer.Formatting,
            NullValueHandling = serializer.NullValueHandling,
            ObjectCreationHandling = serializer.ObjectCreationHandling,
            ReferenceLoopHandling = serializer.ReferenceLoopHandling,
            StringEscapeHandling = serializer.StringEscapeHandling,
            TypeNameHandling = serializer.TypeNameHandling,
            MetadataPropertyHandling = serializer.MetadataPropertyHandling,
            Converters = [.. serializer.Converters]
        };
    }

    public override object? ReadJson(
        JsonReader reader,
        Type objectType,
        object? existingValue,
        JsonSerializer serializer)
    {
        JsonSerializerSettings settings = CloneSerializerSettings(serializer);
        settings.Converters.Remove(this); // Remove this converter to avoid recursion

        JsonSerializer localSerializer = JsonSerializer.Create(settings);
        return localSerializer.Deserialize(reader, objectType);
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(AppAndInstanceConditionEvaluatorAppOrInstance);
    }

    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
    {
        if (value == null)
        {
            return;
        }

        JObject jObject = [];
        DefaultContractResolver resolver = new() { NamingStrategy = new CamelCaseNamingStrategy() };
        foreach (PropertyInfo prop in value.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            if (prop.CanRead)
            {
                object? propValue = prop.GetValue(value);
                if (propValue != null)
                {
                    jObject.Add(resolver.GetResolvedPropertyName(prop.Name), JToken.FromObject(propValue, serializer));
                }
            }
        }

        jObject.WriteTo(writer);
    }
}
bryanapellanes-okta commented 5 months ago

@rcollette Thanks for reporting this. I've added internal issue for tracking and prioritization.

OKTA-735160