okta-sdk-dotnet icon indicating copy to clipboard operation
okta-sdk-dotnet copied to clipboard

AppAndInstanceConditionEvaluatorAppOrInstance ID property setter is private

Open rcollette opened this issue 1 year ago • 2 comments

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 avatar May 28 '24 18:05 rcollette

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);
    }
}

rcollette avatar May 28 '24 19:05 rcollette

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

OKTA-735160

bryanapellanes-okta avatar Jun 03 '24 14:06 bryanapellanes-okta